如何解决用于多线程、偶数驱动状态模式实现的 C++ 基类
我正在使用 C++11 开始一个项目。
在这个项目中,会有很多对象根据它们的状态相互交互;另一方面,软件需要异步处理来自不同接口(UART、互联网、蓝牙...)的大量消息,因此每个对象都应该拥有自己的线程来处理消息/事件。
我正在尝试为我的项目构建一个状态模式基类(状态、状态机、事件),具有以下约束:
- 每个状态机都有自己的线程来处理事件
- 派生类可以定义自己的事件和状态
以下是我目前的实现:
// 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 {}
};
};
代码是一个简化的例子,没有完成。其中有一些我想改善但不知道如何改善的难闻气味:
-
由于 State(s) 总是调用
StateMachineBase::TransitTo()
来触发状态转换,是否可以将TransitTo(shared_ptr<StateBase>)
作为StateBase
的成员函数,它总是会调用 {{1 }}? -
我希望所有继承
GetContext().TransitTo(st)
和EventBase
类的类都有StateBase
,它们在其中定义了状态和事件的类型 ID,但在当前的实现中,有没有这个限制。我应该如何修改代码以强制所有派生类都遵循此规则? -
我的代码中是否还有其他异味?如何改进?
提前致谢。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。