微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

用于多线程、偶数驱动状态模式实现的 C++ 基类

如何解决用于多线程、偶数驱动状态模式实现的 C++ 基类

我正在使用 C++11 开始一个项目。

在这个项目中,会有很多对象根据它们的状态相互交互;另一方面,软件需要异步处理来自不同接口(UART、互联网、蓝牙...)的大量消息,因此每个对象都应该拥有自己的线程来处理消息/事件。

我正在尝试为我的项目构建一个状态模式基类(状态、状态机、事件),具有以下约束:

  1. 每个状态机都有自己的线程来处理事件
  2. 派生类可以定义自己的事件和状态

以下是我目前的实现:


// statemachine.hpp

struct EventBase
{
    EventBase(const std::string name): ev_name(name) {}
    virtual ~EventBase() = default;
    const std::string ev_name;
};
using EventBasePtr = std::shared_ptr<EventBase>;

template<class ContextType>
class StateBase
{
public:
    StateBase(const std::string name): st_name(name) {}
    virtual ~StateBase() = default;

    std::string GetName() const { return st_name; }

    virtual void OnEvent(EventBasePtr ev) = 0;

    virtual void ActionEntry() = 0;
    virtual void ActionExit() = 0;

    virtual ContextType& GetContext() = 0;

private:
    const std::string st_name;
};

template<class ContextType>
class StateMachineBase {
public:
    StateMachineBase(std::shared_ptr<StateBase<ContextType> > st): st_(st) 
    {
        thread_run_ = true;
        ev_proc_thread_ = std::make_shared<std::thread>(&StateMachineBase::EventProcLoop,this);
    }
    virtual ~StateMachineBase() 
    {
        thread_run_ = false;
        if (ev_proc_thread_) {
            ev_proc_thread_->join();
            ev_proc_thread_.reset();
        }
    }

    // NOTE: 
    // dispatchEvent() should always be called in 
    // 1. callback functions
    // 2. msg handler functions
    void dispatchEvent(EventBasePtr ev) {
        ev_queue_.push(ev);
        cv_.notify_one();
    }

    // NOTE: TransitTo() should always be called in
    // 1. OnEvent()
    // 2. StateBase::ActionEntry() (if state base is a transition state)
    void TransitTo(std::shared_ptr<StateBase<ContextType> > st)
    {
        if (st->GetName() == st_->GetName())
        {
            LOG(WARN) << "ignore same-state transition";
            return;
        }
        st_->ActionExit();
        st_ = st;
        st_->ActionEntry();
    }
    std::shared_ptr<StateBase<ContextType> > st_;

private:
    void ProcessEvent(EventBasePtr ev)
    {
        st_->OnEvent(ev);
    }

    void EventProcLoop()
    {
        while (thread_run_)
        {
            std::unique_lock<std::mutex> lk(mtx_cv_);
            cv_.wait(lk,[this] {
                return !thread_run_ || ev_queue_.size() != 0;
            });
            lk.unlock();

            // thread_run_ might be toggled by another thread
            if (!thread_run_)
                break;

            EventBasePtr ev = nullptr;
            mtx_ev_queue_.lock();
            if (!ev_queue_.empty()) {
                ev = ev_queue_.front();
                ev_queue_.pop();
            }
            mtx_ev_queue_.unlock();

            if (ev) {
                ProcessEvent(ev);
            }
        }
    }

    bool thread_run_;
    std::condition_variable cv_;
    std::mutex mtx_cv_;
    std::shared_ptr<std::thread> ev_proc_thread_;

    std::mutex mtx_ev_queue_;
    std::queue<EventBasePtr> ev_queue_;
};

// wificontroller.hpp

class WifiController : public StateMachineBase<WifiController>
{
public:
    static WifiController& GetInstance()
    {
        static WifiController inst;
        return inst;
    }

private:
    WifiController();
    virtual WifiController() = default;

    // =======================================================================
    // state and event declarations
    // =======================================================================
    struct Event : public EventBase {
        enum class Type
        {
            CONNECT_CMD,CONNECT_RESULT,};
        const Type ev_type;
        Event(Type t,std::string n) : ev_type(t),EventBase(n) {}
        virtual ~Event() = default;
    };
    using EventPtr = std::shared_ptr<Event>;

    struct ConnCmdEvent : public Event
    {
        int command; // start(1),stop(0)
        string ssid;
        string password;
        ConnCmdEvent(int cmd,string id,string pw): Event(Type::CONNECT_CMD,"CONNECT_CMD"),command(cmd),ssid(id),password(pw) {}
    };

    struct ConnResEvent : public Event {
        bool success;
        ConnResEvent(bool succ): Event(Type::CONNECT_RESULT,"CONNECT_RESULT"),success(succ) {}
    };
 
    class State : public StateBase<WifiController>
    {
    public:
        enum class Type
        {
            disCONNECTED,CONNECTING,CONNECTED
        };
        State(Type id,std::string name): StateBase(name),st_type_(id) {}

        Type GetType() const { return st_type_; }
        WifiController& GetContext() override { return WifiController::GetInstance(); }
    private:
        const Type st_type_;
    };
    using StatePtr = std::shared_ptr<State>;

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

        void OnEvent(EventBasePtr ev) override 
        {   
            EventPtr event = dynamic_pointer_cast<Event>(ev);
            switch(event->ev_type)
            {
            case Event::Type::CONN_CMD: {
                shared_ptr<ConnCmdEvent> conn_cmd_ev = dynamic_pointer_cast<ConnCmdEvent>(event);
                if (conn_cmd_ev->cmd == 1) {
                    GetContext().TransitTo(make_shared<ConnectingState>(conn_cmd_ev->ssid,conn_cmd_ev->password));
                }
            break;
            }
            default:
                LOG(WARN) << "disconnectedState ignores " << ev->ev_name;
            } // switch
        }
        void ActionEntry() override {}
        void ActionExit() override {}
    };

    class ConnectingState : public State
    {
    public:
        ConnectingState() : State(Type::CONNECTING,"CONNECTING") {}
        void OnEvent(EventBasePtr ev) override 
        {
            case Event::Type::CONN_RESULT: {
                shared_ptr<ConnResEvent> conn_res_ev = dynamic_pointer_cast<ConnResEvent>(event);
                if (conn_res_ev->success) {
                    GetContext().TransitTo(make_shared<ConnectedState>());
                } else {
                    GetContext().TransitTo(make_shared<disconnectedState>());
                }
            break;
            }
            default:
                LOG(WARN) << "ConnectingState ignores " << ev->ev_name;
        }
        void ActionEntry() override { GetContext().ConnectWifi(ssid_,pw_); }
        void ActionExit() override {}
    };
}; 

代码一个简化的例子,没有完成。其中有一些我想改善但不知道如何改善的难闻气味:

  1. 由于 State(s) 总是调用 StateMachineBase::TransitTo() 来触发状态转换,是否可以将 TransitTo(shared_ptr<StateBase>) 作为 StateBase 的成员函数,它总是会调用 {{1 }}?

  2. 我希望所有继承 GetContext().TransitTo(st)EventBase 类的类都有 StateBase,它们在其中定义了状态和事件的类型 ID,但在当前的实现中,有没有这个限制。我应该如何修改代码以强制所有派生类都遵循此规则?

  3. 我的代码中是否还有其他异味?如何改进?

提前致谢。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。