首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >C++中的有限状态机

C++中的有限状态机
EN

Code Review用户
提问于 2018-10-09 06:35:21
回答 3查看 2.3K关注 0票数 4

我做了一个状态机。

您可以按0和1在假设的菜单和播放状态之间切换。

我希望你能帮我进一步改进。

StateManager.h

代码语言:javascript
复制
#pragma once

#ifndef StateManager_H
#define StateManager_H

#include <iostream>
#include "State.h"

/*
State classes
*/

class StateManager {
public:
    // Define m_current_state upon the creation of this class (since that prevents there being a nullptr error in change_state
    StateManager(State* state)
        : m_current_state(state)
    {}

    void change_state(State* state) {
        m_current_state->on_exit();
        m_current_state = state;
        m_current_state->on_enter();
    }

    void update_state(StateManager* manager) {
        m_current_state->on_update(manager);
    }

    State* get_state() {
        return m_current_state;
    }

private:
    State* m_current_state;
};

#endif

State.h

代码语言:javascript
复制
#pragma once

#ifndef State_H
#define State_H

#include "StateManager.h"

// I'm using forward declaration here since I've had a problem with circular dependency
class StateManager;

class State {
public:
    // All states have to have these three functions
    virtual void on_enter() = 0;
    virtual void on_update(StateManager* state_manager) = 0;
    virtual void on_exit() = 0;
};

#endif

IntroState.h

代码语言:javascript
复制
#pragma once

#ifndef IntroState_H
#define IntroState_H

#include "StateManager.h"
#include "State.h"
#include "GameState.h"

/*
Basic test state for demonstration
*/

class MenuState : public State {
public:
    MenuState();

    virtual void on_enter() override;

    virtual void on_update(StateManager* state_manager) override;

    virtual void on_exit() override;

private:
    bool should_change_state;
};

#endif

Introstate.cpp

代码语言:javascript
复制
#include "IntroState.h"

MenuState::MenuState()
    : should_change_state(false)
{}

void MenuState::on_enter(){
    std::cout << "Entering the menu state" << std::endl;
}

void MenuState::on_update(StateManager* state_manager){
    // update the menu
    std::cout << "Should the state be changed to the playing state" << std::endl;
    std::cin >> should_change_state;
    if (should_change_state == true) {
        state_manager->change_state(new PlayingState());
    }
}

void MenuState::on_exit(){
    std::cout << "Exiting the menu state" << std::endl;
}

GameState.h

代码语言:javascript
复制
#pragma once

#ifndef GameState_H
#define GameState_H

#include "IntroState.h"
#include "State.h"

/*
Basic test state for demonstration
*/

class PlayingState : public State {
public:
    PlayingState();

    virtual void on_enter() override;

    virtual void on_update(StateManager* state_manager) override;

    virtual void on_exit() override;

private:
    bool should_change_state;
};

#endif

GameState.cpp

代码语言:javascript
复制
#include "GameState.h"

PlayingState::PlayingState()
    : should_change_state(false)
{}

void PlayingState::on_enter() {
    std::cout << "Entering the playing state" << std::endl;
}

void PlayingState::on_update(StateManager* state_manager) {
    // update the menu
    std::cout << "Should the state be changed to the menu state" << std::endl;
    std::cin >> should_change_state;
    if (should_change_state == true) {
        state_manager->change_state(new MenuState());
    }
}

void PlayingState::on_exit() {
    std::cout << "Exiting the playing state" << std::endl;
}

Source.cpp

代码语言:javascript
复制
#include "StateManager.h"
#include "State.h"
#include "IntroState.h"

int main() {
    StateManager* manager = new StateManager(new MenuState());

    int exit = 0;
    while (exit != 1) {
        manager->update_state(manager);
    }

    return 0;
}

谢谢!

EN

回答 3

Code Review用户

发布于 2018-10-09 10:14:30

最后一件事第一

在我忘记它之前(因为它在您的代码中是外围的),我将指出new StateManager(new MenuState());,它是不属于C++的。原始指针是危险的!如果之后不删除分配的内存,就会留下漏洞(而不是这样)。因此,使用智能指针(std::unique_ptrstd::shared_ptr)代替。这是一个很有趣的话题,但你必须自己学习。或者更好的是,在堆栈上分配对象(State_manager manager;)

设计

manager->update_state(manager);

有点奇怪,你不觉得吗?写manager->update_state(other_manager)意味着什么?可能不多。因此,您可以像这样重写update_state方法:

代码语言:javascript
复制
void update_state() {
    m_current_state->on_update(this); // instead of current->on_update(removed_argument)
}

它更干净,完全等价。但是,你必须写这样的东西:

同时(退出!= 1) { manager->update_state();}

这并不明显。外部输入从何而来?在线程之后,我看到state本身负责管理转换:它将询问用户输入和控制是否从当前状态(即自身)更改到下一个状态(它也必须确定)是否可行。不用说,这是否是一个好的设计,我不知道它是否是有用的,无论如何,有一个国家的经理。它的唯一角色似乎是调用on_enteron_exit,但将其放入State类并不困难,因为on_enteron_exit是其接口的一部分(甚至可以放弃它;只需在update函数的开头和结尾添加指令)。它还跟踪当前状态,但是一个简单的current_state变量可以做到这一点:

代码语言:javascript
复制
std::unique_ptr<State> current_state = std::make_unique<Intro_state>();
while (exit != 1) current_state->update();

但是,您可能也会想知道,为什么这个exit不在状态机之外。如果有一个内部状态,一个游戏状态

在这一点上,我想休息一下,告诉您用同义词或它包含的类的引语命名您的文件是一个非常糟糕的想法。Game_state类应该在Game_state.h中,而不是在gaming_state.h中,这没有任何意义。

那为什么没有退出状态呢?它可以做一些清理,显示学分,我不知道。所以现在我们有:

代码语言:javascript
复制
std::unique_ptr<State> current_state = std::make_unique<Intro_state>();
current_state->update();

但是current_state变量本身以及随之而来的分配也是多余的。Intro_state().update();看起来会很奇怪,但是intro();呢?因此,我们有一个函数void intro(),它询问它是需要调用void game()还是void exit(),还有一个void game(),它询问是否需要再次调用void intro()void exit()。设计模式已经消失,但我们没有删除任何功能或可用性功能,只是简化了。从这个观察,我觉得我可以安全地得出结论,在你的设计中有一个问题。

我的3分钱

许多C++手册坚持使用面向对象的语言和所谓的设计模式。这并不是,IMHO (也就是说,它不是我自己的,而是被C++现场的大多数著名人物所采用的),是学习C++的好方法。尝试探索语言的鲁棒性多功能性

票数 2
EN

Code Review用户

发布于 2018-10-09 09:03:45

只在该文件中包含实际需要的标题。无需在StateManager.h中使用#include <iostream>

到处都是泄密。你new每个州,但我没有看到任何地方你delete他们。使用智能指针,或者让州清理自己的on_exit。不过,我更喜欢聪明的指针。

change_state中调用on_update将导致令人惊讶的效果,因为在此期间调用了on_exit。

相反,返回下一个状态:

代码语言:javascript
复制
void update_state(StateManager* manager) {
    unique_ptr<State> tmp = m_current_state->on_update(manager);
    if(tmp != nullptr)
        change_state(std::move(tmp));
}
票数 1
EN

Code Review用户

发布于 2018-11-08 13:54:57

On原始指针

在我看来,游戏是一个地方的原始指针是可以接受的。它们是可以的,因为您希望严格控制对象的创建和销毁,并且通常在活动播放期间避免内存分配,因此您从智能指针获得的功能不是特别有价值的,主要是在Debug构建中混乱代码和减慢速度。也就是说,如果您正在学习C++,您确实想学习智能指针;它们是重要的和有用的。

无论如何,您滥用原始指针是因为您没有仔细管理对象的生存期。您必须、必须、必须考虑对象在何时何地会在新对象更新时被删除。你不能忽视这一点。就你而言,我认为你做错了:

您可能希望将状态保持为永久对象

您可能希望只创建一次状态(将它们存储在将管理其生存期的对象中),而不是每次进入菜单状态时执行new MenuState操作,然后在更改状态时将指向现有新状态的指针传递给StateManager。您还可以考虑隐藏指针,而是为新状态传递一条合适的消息。

您可能希望推迟状态更改

当状态发生变化时,您可能希望将更改推迟到游戏更新循环中的适当点,这样您就可以正确地同步菜单上的声音/图形/等等。您还可能希望将退出和条目分离,以便您可以从一个菜单转换到另一个菜单,这意味着您希望在下一个菜单在接下来的几个框架中开始其条目之前,通过几个框架更新其退出。

票数 1
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/205202

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档