【问题标题】:Segmentation fault when implementing state pattern in C++在 C++ 中实现状态模式时出现分段错误
【发布时间】:2021-07-10 21:28:44
【问题描述】:

我正在尝试在 C++ 中实现 State 模式。 这里是一个示例代码:

#include <cstdio>

class Context;

class State {
public:
    virtual ~State() = default;

    explicit State(Context *context) : context_(context) {}

    virtual void Print() = 0;

    virtual void ChangeState() = 0;

protected:
    Context *const context_;
};

class Context {
public:
    void SwitchState(State *state) {
        delete this->current_state_;
        this->current_state_ = state;
    }

    State *GetCurrentState() const {
        return this->current_state_;
    }

private:
    State *current_state_ = nullptr;
};

class FirstState : public State {
public:
    explicit FirstState(Context *context) : State(context) {}

    ~FirstState() override {
        printf("FirstState destructed. Address: %p\n", this);
    }

    void Print() override {
        printf("First state print..\n");
    }

    void ChangeState() override;
};

class SecondState : public State {
public:
    explicit SecondState(Context *context) : State(context) {}

    void Print() override {
        printf("Second state print..\n");
    }

    void ChangeState() override;
};

void FirstState::ChangeState() {
    printf("First state changed to second state\n");
    this->context_->SwitchState(new SecondState(this->context_));
    printf("First state changed to first state\n");
    this->context_->SwitchState(new FirstState(this->context_));
    printf("First state changed to second state\n");
    this->context_->SwitchState(new SecondState(this->context_));
}

void SecondState::ChangeState() {
    this->context_->SwitchState(new FirstState(this->context_));
    this->context_->SwitchState(new SecondState(this->context_));
    this->context_->SwitchState(new FirstState(this->context_));
}

int main() {
    Context context;

    context.SwitchState(new FirstState(&context));

    context.GetCurrentState()->Print();
    context.GetCurrentState()->ChangeState();
    context.GetCurrentState()->Print();
    context.GetCurrentState()->ChangeState();

    return 0;
}

我有一个分段错误错误,因为在这个实现中从State 调用Context::SwitchState 后,当前状态被删除,从 SwitchState 方法返回后我不能使用this,但有时我必须。 我尝试使用共享指针,它有点帮助,因为我不再遇到分段错误,但由于某种原因,状态的析构函数无论如何都会被调用。 我需要在切换状态时创建新的状态实例,因为我需要将一些参数传递给更改后的状态。 另外,我一般不知道如何解决这个问题,因为切换状态后所有对状态的引用都丢失了,只剩下引用是this。 我有一些不好的想法可以解决这个问题,但它们很丑陋,例如让SwitchState 只是保存新状态,并在状态中每个方法的末尾调用一些RealSwitchSavedState。我认为它会解决问题,因为在这种情况下,对状态的引用会一直保留到方法结束,但代码会很丑。

希望得到您的帮助! 提前致谢!

【问题讨论】:

  • "但由于某种原因,状态的析构函数仍然被调用" 你在Context::SwitchState 中调用delete this-&gt;current_state_。其中.. 删除状态,并调用其析构函数。而且,因为,本质上,您从State::ChangeState 的实现中删除了调用该方法的状态-取消引用该状态的this 是删除后的UB。仅使第一个Context::SwitchState 调用非UB。在我看来,最好的办法是重新思考整个关系架构。
  • this-&gt;context_ 保存在FirstState::ChangeState 顶部的局部变量中,将该变量用于后续SwitchState 调用,因此您无需触摸this。仍然是一个非常脆弱的设计,但它会解决你眼前的问题。
  • @AlgirdasPreidžius,是的,我知道为什么会这样,但我想找到具有相同架构的最佳解决方案,因为在 Java 中我可以轻松实现它,在 C++ 中我面临这个问题。跨度>
  • @IgorTandetnik,谢谢,但在真实代码中,我需要访问完整的对象状态,而不仅仅是上下文。我现在可以将需要的数据保存到局部变量中,但这不是最佳解决方案
  • 暂时忘记您的代码。你能用语言解释应该发生什么吗?特别是SwitchState为什么逻辑一致,不涉及*this的销毁?

标签: c++ oop memory design-patterns state-pattern


【解决方案1】:

这里有很多分配。我很好奇您是否可以改用std::variant 来实现您想要的。

实施

#include <variant>
#include <type_traits>
#include <iostream>

template<typename... Ts> struct Overload : Ts... { using Ts::operator( )...; };
template<typename... Ts> Overload( Ts... ) -> Overload<Ts...>;

class StateBase { 
public:
    virtual void print_state( ) const = 0;
    virtual ~StateBase( ) = default;
};

class FirstState : public StateBase {
public:
    void print_state( ) const override {
        std::cout << "First state\n";
    }
};

struct SecondState : public StateBase {
public:
    void print_state( ) const override { 
        std::cout << "Second state\n";
    }
};

struct ThirdState : public StateBase {
public:
    void print_state( ) const override { 
        std::cout << "Third state\n";
    }
};

template<typename ...States>
class Fsm { 
    static_assert( ( std::is_base_of_v<StateBase, States> && ... ), "State must derive from StateBase" );

public:
    template<typename T, 
             typename En = std::enable_if_t<std::is_base_of_v<StateBase, T>>>
    explicit Fsm( T initial_state ) 
        : state_{ std::move( initial_state ) }
    { }

    template<typename T, 
             typename En = std::enable_if_t<std::is_base_of_v<StateBase, T>>>
    void change_state( T state ) {
        state_ = std::move( state );
    }

    template<typename T, 
         typename En = std::enable_if_t<std::is_base_of_v<StateBase, T>>,
         typename ...Args>
    void change_state( Args&&... args ) {
        state_.template emplace<T>( std::forward<Args>( args )... );
    }

    void print_state( ) {
        std::visit( Overload { [ ]( const StateBase& s ) { 
            s.print_state( ); } }, state_ );
    }
private:
    std::variant<States...> state_;
};

用法

int main( ) {
    Fsm<FirstState, SecondState, ThirdState> fsm{ ThirdState{ } };
    // Prints Third State.
    fsm.print_state( );

    fsm.change_state( SecondState{ } );

    // Prints Second State.
    fsm.print_state( );

    fsm.change_state<FirstState>( );
    
    // Prints First State.
    fsm.print_state( );
}

现在我不确切知道您的用例是什么,或者这是否满足您的要求,但您可以更改此示例以实现您想要的功能。

【讨论】:

    猜你喜欢
    • 2020-06-01
    • 2020-09-30
    • 1970-01-01
    • 1970-01-01
    • 2020-09-10
    • 2015-02-14
    • 1970-01-01
    • 1970-01-01
    • 2013-04-11
    相关资源
    最近更新 更多