【问题标题】:unique_ptr state object lifetime in state machine when pushing new state in enter method在进入方法中推送新状态时,状态机中的 unique_ptr 状态对象生命周期
【发布时间】:2019-04-10 19:43:51
【问题描述】:

我正在创建一个状态机,其中几个状态正在enter() 方法中将机器切换到新的(下一个)状态。状态是 unique_ptr 对象,根据需要创建并提供给机器。

由于某些状态在 enter() 方法中切换到下一个状态(因此,在使用时),我担心这里可能出现问题 - 当当前状态调用 set_state() 时,状态机为其 @987654326 分配一个新状态@ 成员因此丢失了指向进行此调用的状态的唯一指针。

下面是我关心的一个例子 - obj A 和 B 的唯一 unique_ptr 设置为指向 C,同时从 A 然后 B 递归调用此操作。这可靠吗?会不会有什么问题?

#include <memory>
#include <iostream>

class IState;
class IStateMachine {
public:
    virtual ~IStateMachine() = default;
    virtual void set_state(std::unique_ptr<IState> new_state) = 0;
};

class IState {
public:
    virtual ~IState() = default;
    virtual void enter(IStateMachine&) = 0;
};

class StateC : public IState {
public:
    void enter(IStateMachine&) override {
        std::cout <<  __func__ << ": StateC - start" << std::endl;
        std::cout <<  __func__ << ": StateC - end" << std::endl;
    }
};

class StateB : public IState {
public:
    void enter(IStateMachine& sm) override {
        std::cout <<  __func__ << ": StateB - start" << std::endl;
        sm.set_state(std::make_unique<StateC>());
        std::cout <<  __func__ << ": StateB - end" << std::endl;
    }
};

class StateA : public IState {
public:
    void enter(IStateMachine& sm) override {
        std::cout <<  __func__ << ": StateA - start" << std::endl;
        sm.set_state(std::make_unique<StateB>());
        std::cout <<  __func__ << ": StateA - end" << std::endl;
    }
};

class StateMachine : public IStateMachine {
public:
    void start() {
        set_state(std::make_unique<StateA>());
    }

    void set_state(std::unique_ptr<IState> new_state) {
        state_ = std::move(new_state);
        state_->enter(*this);
    }

    std::unique_ptr<IState> state_;
};

int main()
{
    StateMachine sm;
    sm.start();
}

代码输出:

enter: StateA - start
enter: StateB - start
enter: StateC - start
enter: StateC - end
enter: StateB - end
enter: StateA - end

编辑 1:

背后的主要思想是状态根据需要创建,并在不再需要后自动销毁。 限制是状态可以在其enter() 方法中做一些工作,然后在该方法结束时将状态机切换到下一个状态。

编辑 2:

我没有明确删除该对象。我的问题更多的是关于 unique_ptr 指向的对象的生命周期,以防从对象自己的方法(递归)分配给这个 unique_ptr 的新对象(参见示例代码)。

【问题讨论】:

  • 官方称其为未定义行为,但由于您在删除后无法访问任何成员,因此它可能适用于大多数平台
  • this 在调用 sm.set_state 后悬空,这本身不是 UB。但是在this dangles 时引用类内的任何内容都是UB。无论如何看起来都是糟糕的设计。
  • 如果你能在这里解释更多关于糟糕设计的信息,我将不胜感激,并可能提出改进的方法。我在问题的底部添加了我的意图。
  • 你可以让 set_state() 返回之前的状态。然后调用者可以决定自己的生命周期。
  • “我不认为它是重复的”。我看不出显式删除 this 或通过 unique_ptr 删除之间的区别。 is delete this allowed 中的一些答案也谈到了这一点

标签: c++ unique-ptr state-machine


【解决方案1】:

您可以尝试添加中间步骤:

enter() 方法中,您可以告诉机器您想要什么样的状态,而不是直接将新状态推送到您的状态机(这会删除实际要求您的机器更改状态的当前状态)推。这会产生一种等待处理的待处理推送。

为此,您可以有一个枚举和一个映射,将枚举的每个成员与实际状态相关联。然后,您可以处理待处理的推送,查看您想要直接在状态机中推送的状态。

但要这样做,就需要再次回到机器上,因此各州无法独自完成所有工作。

看起来是这样的:

enum States
{
    State_A,
    //...
};

class StateMachine : public IStateMachine {
public:
    StateMachine () {
        fabric[State_A] = [] () { return std::unique_ptr<IState>(new StateA()); }
       // ....
    }

    void push(States state) { 
        pendingState_ = std::move(state); 
    }

    void update()
    {
        auto found = fabric_.find(pendingState_);
        assert(found != fabric_.end());
        state_ = found->second();
        state_->enter(*this);
    }

   // ... same as before ...
private:
    States                  pendingState_;
    std::map<States, std::function<std::unique_ptr<IState>()> > fabric_;
};

现在在输入方法中,它将是:

sm.push(/*a state*/);

主要是让它循环滚动。所以这意味着您还需要实现一种方法来检查工作何时完成,例如您可以创建枚举None 的成员并检查机器的状态是否为无。

【讨论】:

    猜你喜欢
    • 2020-06-01
    • 1970-01-01
    • 2018-06-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-03-07
    • 1970-01-01
    相关资源
    最近更新 更多