【C++委托与事件】函数指针,回调机制,事件式编程与松耦合的设计模式(下)

发布于:2025-02-21 ⋅ 阅读:(13) ⋅ 点赞:(0)

前言

  • 本文我们接着来讲讲博主最近在项目中频繁使用的,也就是广泛运用于C#或者Java的一个常用编程机制(思路)-----委托事件。由于C++在语言特性上没有像C#那样直接支持委托事件,因此本文我们借着学习这两个新的机制,学习一下如何在C++中复刻委托事件

  • 本系列:

  • 上一篇文章我们介绍了C++中如何实现委托和事件,并且通过委托和事件实现了观察者模式

    • 委托:安全类型的函数指针,我们使用std::function来编写、
    • 事件:特殊的委托,允许对象向外界通知某些事情的发生,但它只能由事件发布者触发,外部只能通过订阅(绑定)方法来响应事件。
  • 本期本系列下半我们来谈谈:

    1. 基于C++委托事件实现 发布-订阅模式`
    2. 模拟ROS2进行多话题发布-订阅模式的编写
    3. 基于C++委托事件实现状态机

1 基于C++委托事件实现 发布-订阅模式`

1-1 概念
  • 发布-订阅模式(Publisher-Subscriber Pattern),是一种常见的行为型设计模式,也被称为观察者模式的变种。这种模式通过将事件的发布和接收解耦合,让一个系统中的多个组件之间的交互变得更加灵活,尤其适用于消息传递和事件处理场景。
  • 发布-订阅模式的主要组成部分:
    • Publisher(发布者):发布消息的实体,通常不直接知道有谁订阅了它的事件。
    • Subscriber(订阅者):订阅事件并接收消息的实体。订阅者对某个特定事件感兴趣,会注册自己到事件总线中,等待事件的发生。
    • EventBus(事件总线):作为事件的传递通道,负责协调发布者和订阅者之间的消息传递。它可以存储订阅者的回调函数,并在发布者触发事件时,通知所有订阅该事件的订阅者。
1-2 发布-订阅模式观察者模式的区别
  • 一句话概括就是:发布-订阅模式由第三方来管理发布和订阅,观察者模式是直接由发布者管理
特性 观察者模式(Observer Pattern) 发布-订阅模式(Publisher-Subscriber Pattern)
模式类型 行为型设计模式 行为型设计模式
耦合度 观察者和主题(发布者)之间存在直接依赖关系,观察者直接注册到主题上 发布者与订阅者之间解耦,发布者不直接知道谁订阅了它的事件
通知机制 主题对象(Subject)直接通知其所有观察者(Observers) 通过事件总线或消息队列通知订阅者,发布者不直接通知订阅者
发布者与订阅者的关系 发布者(主题)知道所有的观察者(订阅者)并维护它们 发布者与订阅者之间没有直接联系,事件总线或消息中介负责事件传递
订阅机制 观察者通过直接注册到主题上进行订阅 订阅者通过事件总线或消息队列进行订阅
扩展性 扩展性较差,增加新的观察者可能需要修改主题(发布者) 扩展性强,可以随时增加新的订阅者或事件类型,且无需修改发布者
事件传播方式 主题触发时直接传递给观察者 通过中介(事件总线、消息队列等)分发给订阅者
事件流向 一对多,主题和所有观察者有直接的联系 多对多,发布者与多个订阅者通过事件总线解耦
1-3 UML类图
subscribes
1
*
uses
1
1
listens
1
*
EventBus
+subscribe(callback: EventCallback)
+publish(event: String)
Publisher
+publishEvent(event: String)
Subscriber
+onEventReceived(event: String)
1-4 代码示例:基于C++委托事件实现 发布-订阅模式
  • 根据上述的概念,我们首先先来编写事件总线 类,他需要包括两种方法:
    • 发布方法:通知所有订阅者
    • 订阅方法:添加到订阅队列
    • 一个事件,这里我定义回调函数的函数签名为void(const std::string&),并使用std::function来管理
  • 那么我们有:
#include <iostream>
#include <string>
#include <vector>
#include <functional>
#include <algorithm>

// 定义一个事件总线类
class EventBus {
public:
    // 使用 std::function 来存储订阅者的回调函数
    using EventCallback = std::function<void(const std::string&)>;

    // 订阅事件,传入一个回调函数
    void subscribe(EventCallback callback) {
        subscribers.push_back(callback);
    }

    // 发布事件,通知所有订阅者
    void publish(const std::string& event) {
        std::cout << "Event published: " << event << std::endl;
        for (auto& subscriber : subscribers) {
            subscriber(event);  // 通知每个订阅者
        }
    }

private:
    std::vector<EventCallback> subscribers;  // 存储所有订阅者的回调函数
};

  • 紧接着我们来编写发布者和订阅者
// 发布者类
class Publisher {
public:
    Publisher(EventBus& eventBus) : eventBus(eventBus) {}

    // 发布事件
    void publishEvent(const std::string& event) {
        eventBus.publish(event);
    }

private:
    EventBus& eventBus;  // 引用事件总线
};

// 订阅者类
class Subscriber {
public:
    Subscriber(const std::string& name) : name(name) {}

    // 当事件发布时,订阅者的回调函数
    void onEventReceived(const std::string& event) {
        std::cout << name << " received event: " << event << std::endl;
    }

private:
    std::string name;  // 订阅者的名字
};

  • 完整代码:
#include <iostream>  
#include <string>  
#include <vector>  
#include <functional>  
#include <algorithm>  
  
// 定义一个事件总线类  
class EventBus {  
public:  
    // 使用 std::function 来存储订阅者的回调函数  
    using EventCallback = std::function<void(const std::string&)>;  
  
    // 订阅事件,传入一个回调函数  
    void subscribe(EventCallback callback) {  
        subscribers.push_back(callback);  
    }  
  
    // 发布事件,通知所有订阅者  
    void publish(const std::string& event) {  
        std::cout << "Event published: " << event << std::endl;  
        for (auto& subscriber : subscribers) {  
            subscriber(event);  // 通知每个订阅者  
        }  
    }  
  
private:  
    std::vector<EventCallback> subscribers;  // 存储所有订阅者的回调函数  
};  
// 发布者类  
class Publisher {  
public:  
    Publisher(EventBus& eventBus) : eventBus(eventBus) {}  
  
    // 发布事件  
    void publishEvent(const std::string& event) {  
        eventBus.publish(event);  
    }  
  
private:  
    EventBus& eventBus;  // 引用事件总线  
};  
  
// 订阅者类  
class Subscriber {  
public:  
    Subscriber(const std::string& name) : name(name) {}  
  
    // 当事件发布时,订阅者的回调函数  
    void onEventReceived(const std::string& event) {  
        std::cout << name << " received event: " << event << std::endl;  
    }  
  
private:  
    std::string name;  // 订阅者的名字  
};  
int main() {  
    // 创建事件总线  
    EventBus eventBus;  
  
    // 创建发布者,传入事件总线  
    Publisher publisher(eventBus);  
  
    // 创建订阅者  
    Subscriber subscriber1("Subscriber 1");  
    Subscriber subscriber2("Subscriber 2");  
  
    // 订阅事件  
    eventBus.subscribe(std::bind(&Subscriber::onEventReceived,&subscriber1,std::placeholders::_1));  
    eventBus.subscribe(std::bind(&Subscriber::onEventReceived,&subscriber2,std::placeholders::_1));  
  
    // 发布一个事件  
    publisher.publishEvent("New Article Published");  
  
    return 0;  
}
  • 同样这里我们绑定类内函数使用std::bind(上一期讲过了的)
eventBus.subscribe(std::bind(&Subscriber::onEventReceived,&subscriber1,std::placeholders::_1));
  • 可以看到,发布者调用总线的发布方法,并不需要知道所有的订阅者就能通知所有订阅者请添加图片描述

  • 相信聪明的你一定觉得,上述例子仍然没有能充分区分开观察者模式发布订阅模式
  • 那么我们来看看发布订阅模式的进阶用法:去维护多组发布订阅,并且管理通讯之间的话题。

2 发布-订阅模式进阶–不同话题的发布和订阅

2-1 ROS的发布订阅模型(扩展)
  • 接触过ROSROS2的朋友一定不陌生,在 ROS2 中,发布者和订阅者通过 话题(Topic) 来进行通讯。我们通过创建 Publisher(发布者)Subscriber(订阅者) 节点来实现消息的发布和接收。
  • 这里我们来看ROS2的一个基础的发布订阅代码:
  • 发布方
#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/string.hpp"

using std::placeholders::_1;

class MinimalPublisher : public rclcpp::Node
{
public:
    MinimalPublisher() : Node("minimal_publisher")
    {
        publisher_ = this->create_publisher<std_msgs::msg::String>("topic", 10);
        timer_ = this->create_wall_timer(
            std::chrono::seconds(1),
            std::bind(&MinimalPublisher::timer_callback, this)
        );
    }

private:
    void timer_callback()
    {
        auto message = std_msgs::msg::String();
        message.data = "Hello, ROS2!";
        RCLCPP_INFO(this->get_logger(), "Publishing: '%s'", message.data.c_str());
        publisher_->publish(message);
    }

    rclcpp::Publisher<std_msgs::msg::String>::SharedPtr publisher_;
    rclcpp::TimerBase::SharedPtr timer_;
};

int main(int argc, char ** argv)
{
    rclcpp::init(argc, argv);
    rclcpp::spin(std::make_shared<MinimalPublisher>());
    rclcpp::shutdown();
    return 0;
}

  • 接收方:
#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/string.hpp"

class MinimalSubscriber : public rclcpp::Node
{
public:
    MinimalSubscriber() : Node("minimal_subscriber")
    {
        subscription_ = this->create_subscription<std_msgs::msg::String>(
            "topic", 10, std::bind(&MinimalSubscriber::topic_callback, this, _1)
        );
    }

private:
    void topic_callback(const std_msgs::msg::String::SharedPtr msg) const
    {
        RCLCPP_INFO(this->get_logger(), "Received: '%s'", msg->data.c_str());
    }

    rclcpp::Subscription<std_msgs::msg::String>::SharedPtr subscription_;
};

int main(int argc, char ** argv)
{
    rclcpp::init(argc, argv);
    rclcpp::spin(std::make_shared<MinimalSubscriber>());
    rclcpp::shutdown();
    return 0;
}

  • 没有接触过的朋友也不要紧,这里说明一下:
  • 发布者(Publisher):在 ROS2 中,发布者通过 create_publisher 函数来创建一个发布者,并将其与特定的 话题topic)相关联。
    • 这里发布者注册了一个名为"topic"的话题
    • 同时这里指定了事件(回调函数)的函数类型为void(*)(std_msgs::msg::String)
publisher_ = this->create_publisher<std_msgs::msg::String>("topic", 10);
  • 也就是当发布者进行发布的时候,会通知所有订阅了"topic"这个话题的所有订阅者
publisher_->publish(message);
  • 订阅者(Subscriber):订阅者通过 create_subscription 函数来创建一个订阅者,并指==定订阅的 话题topic)==以及回调函数。在这个例子中,订阅者会接收到来自发布者发布的消息,并通过回调函数打印消息内容。
    • 同理这里我们订阅方注册了话题为"topic"
    • 并绑定了当发布者进行更新的时候需要执行的事件(回调函数)topic_callback
 subscription_ = this->create_subscription<std_msgs::msg::String>(
            "topic", 10, std::bind(&MinimalSubscriber::topic_callback, this, _1)
        );
  • 不难发现,当出现多组多对多不同话题进行通讯的时候,单纯的观察者模式就会宕机,为此发布订阅模式把这个处理逻辑递交给事件管理总线,在上述例子中也就是rclcpp内部实现的。
2-2 实现话题-发布订阅模型(进阶):实现发布者类和订阅者类
  • 那么同样道理,我们可以之间使用C++的委托和事件实现上述功能,同时我们借助模板,仿造类型的发布订阅逻辑
2-2-1 前向声明 EventBus
  • 由于后续的Subscriber 类需要引用 EventBus 类型的指针或引用,而编译器需要知道 EventBus 类型。所以我们需要先进行前向声明总线类,表面传递的是MsgType
// 订阅者类  
template <typename MsgType>  
class EventBus; 
2-2-2 Publisher
// 发布者类  
template <typename MsgType>  
class Publisher  
{  
public:  
    Publisher(std::shared_ptr<EventBus<MsgType>> bus, const std::string &topic)  
            : eventBus(bus), topic(topic) {}  
  
    // 发布消息到事件总线  
    void publish(const MsgType &message)  
    {  
        eventBus->publish(topic, message);  // 将消息发布到特定的话题  
    }  
  
private:  
    std::shared_ptr<EventBus<MsgType>> eventBus;  // 引用事件总线  
    std::string topic;                            // 发布者的主题  
};
2-2-3 Subscriber
  • 同样道理我们指定订阅者
    • Subscriber 是一个模板类,允许我们为任意类型的消息(MsgType)创建订阅者。
    • 在构造时,Subscriber 需要提供一个名字和一个回调函数。回调函数是一个 std::function 对象,用来处理接收到的消息。
    • onMessageReceived:当事件总线发布消息时,Subscriber 会调用这个函数来执行它的回调函数。
template <typename MsgType>  
class Subscriber  
{  
public:  
    Subscriber(const std::string &name, std::function<void(const MsgType &)> callback)  
            : subscriberName(name), callbackFn(callback) {}  
  
    // 当接收到消息时调用回调函数  
    void onMessageReceived(const MsgType &message)  
    {  
        callbackFn(message);  
    }  
  
private:  
    std::string subscriberName;  
    std::function<void(const MsgType &)> callbackFn;  // 回调函数  
};

2-3 实现话题-发布订阅模型(进阶):实现事件总线类EventBus
  • 紧接着我们来看最为重要的事件总线类EventBus,先放上完整的代码,然后我们详细分析
  
// 事件总线类  
template <typename MsgType>  
class EventBus : public std::enable_shared_from_this<EventBus<MsgType>> // 继承 enable_shared_from_this{  
public:  
    using MessageType = MsgType;  
    using CallbackType = std::function<void(const MessageType &)>;  
  
    // 创建订阅者并将其添加到话题订阅者列表中  
    std::shared_ptr<Subscriber<MsgType>> create_subscription(const std::string &topic, CallbackType callback)  
    {  
        auto subscriber = std::make_shared<Subscriber<MsgType>>(topic, callback);  
        subscribers[topic].push_back(subscriber);  // 将订阅者添加到该话题的订阅者列表中  
        return subscriber;  
    }  
  
    // 创建发布者  
    std::shared_ptr<Publisher<MsgType>> create_publisher(const std::string &topic)  
    {  
        return std::make_shared<Publisher<MsgType>>(this->shared_from_this(), topic);  
    }  
  
    // 发布消息到指定话题  
    void publish(const std::string &topic, const MsgType &message)  
    {  
        auto it = subscribers.find(topic);  
        if (it != subscribers.end())  // 检查该话题是否有订阅者  
        {  
            for (const auto &subscriber : it->second)  
            {  
                subscriber->onMessageReceived(message);  // 调用订阅者的回调函数  
            }  
        }  
        else  
        {  
            std::cout << "No subscribers for topic: " << topic << std::endl;  
        }  
    }  
  
private:  
    std::unordered_map<std::string, std::vector<std::shared_ptr<Subscriber<MsgType>>>> subscribers;  // 存储话题和其订阅者  
};
2-3-1 使用shared_from_this()传递给发布者类
  • 我们回顾一下上面发布者类中需要事件类来调用事件总线类EventBus的publish方法
 // 发布消息到事件总线  
void publish(const MsgType &message)  
{  
	eventBus->publish(topic, message);  // 将消息发布到特定的话题  
}  
  • 因此在事件总线类EventBus中我们需要被本体的智能指针传递给发布者类
// 创建发布者  
std::shared_ptr<Publisher<MsgType>> create_publisher(const std::string &topic)  
{  
	return std::make_shared<Publisher<MsgType>>(this->shared_from_this(), topic);  
}
  • 在智能指针章节我们提到过,继承 std::enable_shared_from_this<EventBus<MsgType>>允许对象通过 shared_from_this() 获得一个指向自己对象的 shared_ptr请添加图片描述

  • 发布者类就可以使用this->shared_from_this()传递事件总线类EventBus

// 创建发布者  
    std::shared_ptr<Publisher<MsgType>> create_publisher(const std::string &topic)  
    {  
        return std::make_shared<Publisher<MsgType>>(this->shared_from_this(), topic);  
    }  

2-3-2 订阅者类创建与事件存储
  • 根据我们的定义,我们定义事件为如下
using MessageType = MsgType;  
using CallbackType = std::function<void(const MessageType &)>;  
  • 同时我们维护一个映射函数,用于存储话题和函数的对应关系
 std::unordered_map<std::string, std::vector<std::shared_ptr<Subscriber<MsgType>>>> subscribers;  // 存储话题和其订阅者  
  • 那么每次我们创建订阅者就只需要添加到对应的容器中
// 创建订阅者并将其添加到话题订阅者列表中  
std::shared_ptr<Subscriber<MsgType>> create_subscription(const std::string &topic, CallbackType callback)  
{  
	auto subscriber = std::make_shared<Subscriber<MsgType>>(topic, callback);  
	subscribers[topic].push_back(subscriber);  // 将订阅者添加到该话题的订阅者列表中  
	return subscriber;  
}  
2-3-3 完整代码与输出
  • 完整代码如下
#include <iostream>  
#include <unordered_map>  
#include <vector>  
#include <functional>  
#include <memory>  
#include <string>  
  
// 订阅者类  
template <typename MsgType>  
class EventBus;  // Forward declaration for EventBus  
  
template <typename MsgType>  
class Subscriber  
{  
public:  
    Subscriber(const std::string &name, std::function<void(const MsgType &)> callback)  
            : subscriberName(name), callbackFn(callback) {}  
  
    // 当接收到消息时调用回调函数  
    void onMessageReceived(const MsgType &message)  
    {  
        callbackFn(message);  
    }  
  
private:  
    std::string subscriberName;  
    std::function<void(const MsgType &)> callbackFn;  // 回调函数  
};  
  
// 发布者类  
template <typename MsgType>  
class Publisher  
{  
public:  
    Publisher(std::shared_ptr<EventBus<MsgType>> bus, const std::string &topic)  
            : eventBus(bus), topic(topic) {}  
  
    // 发布消息到事件总线  
    void publish(const MsgType &message)  
    {  
        eventBus->publish(topic, message);  // 将消息发布到特定的话题  
    }  
  
private:  
    std::shared_ptr<EventBus<MsgType>> eventBus;  // 引用事件总线  
    std::string topic;                            // 发布者的主题  
};  
  
// 事件总线类  
template <typename MsgType>  
class EventBus : public std::enable_shared_from_this<EventBus<MsgType>> // 继承 enable_shared_from_this{  
public:  
    using MessageType = MsgType;  
    using CallbackType = std::function<void(const MessageType &)>;  
  
    // 创建订阅者并将其添加到话题订阅者列表中  
    std::shared_ptr<Subscriber<MsgType>> create_subscription(const std::string &topic, CallbackType callback)  
    {  
        auto subscriber = std::make_shared<Subscriber<MsgType>>(topic, callback);  
        subscribers[topic].push_back(subscriber);  // 将订阅者添加到该话题的订阅者列表中  
        return subscriber;  
    }  
  
    // 创建发布者  
    std::shared_ptr<Publisher<MsgType>> create_publisher(const std::string &topic)  
    {  
        return std::make_shared<Publisher<MsgType>>(this->shared_from_this(), topic);  
    }  
  
    // 发布消息到指定话题  
    void publish(const std::string &topic, const MsgType &message)  
    {  
        auto it = subscribers.find(topic);  
        if (it != subscribers.end())  // 检查该话题是否有订阅者  
        {  
            for (const auto &subscriber : it->second)  
            {  
                subscriber->onMessageReceived(message);  // 调用订阅者的回调函数  
            }  
        }  
        else  
        {  
            std::cout << "No subscribers for topic: " << topic << std::endl;  
        }  
    }  
  
private:  
    std::unordered_map<std::string, std::vector<std::shared_ptr<Subscriber<MsgType>>>> subscribers;  // 存储话题和其订阅者  
};  
  
// 回调函数  
void callback1(const std::string &message)  
{  
    std::cout << "callback1 received message: " << message << std::endl;  
}  
  
void callback2(const std::string &message)  
{  
    std::cout << "callback2 received message: " << message << std::endl;  
}  
  
int main()  
{  
  
    auto eventBus = std::make_shared<EventBus<std::string>>();  
  
    auto publisher1 = eventBus->create_publisher("/topic1");  
    auto publisher2 = eventBus->create_publisher("/topic2");  
  
    auto subscriber1 = eventBus->create_subscription("/topic1", callback1);  
  
  
    auto subscriber2 = eventBus->create_subscription("/topic2", callback2);  
  
  
    publisher1->publish("Message for topic 1");  
    publisher2->publish("Message for topic 2");  
  
    return 0;  
}
  • 可以看到对应话题的消息一旦发布以后,订阅方的回调函数就被直接调用了。请添加图片描述



3 基于C++委托事件实现状态机

3-1 概念与设计
3-2 代码编写
  • 我们编写如下状态机类代码:
  • 老规矩先上代码然后细说
#include <iostream>
#include <functional>
#include <map>
#include <string>

// 状态机类
class StateMachine {
public:
    using State = std::string;
    using Event = std::string;
    using StateHandler = std::function<void()>;

    // 添加状态和相应的处理函数
    void AddState(const State& state, StateHandler handler) {
        stateHandlers[state] = handler;
    }

    // 添加事件和相应的状态转换
    void AddTransition(const State& fromState, const Event& event, const State& toState) {
        transitions[{fromState, event}] = toState;
    }

    // 触发事件,并根据当前状态和事件进行状态转换
    void TriggerEvent(const Event& event) {
        auto transitionKey = std::make_pair(currentState, event);
        if (transitions.find(transitionKey) != transitions.end()) {
            // 转到新的状态
            currentState = transitions[transitionKey];
            std::cout << "Transitioning to state: " << currentState << std::endl;
            // 执行新的状态的处理函数
            stateHandlers[currentState]();
        } else {
            std::cout << "No transition available for event: " << event << " from state: " << currentState << std::endl;
        }
    }

    // 设置初始状态
    void SetInitialState(const State& state) {
        currentState = state;
    }

private:
    State currentState;
    std::map<State, StateHandler> stateHandlers;  // 状态与处理函数的映射
    std::map<std::pair<State, Event>, State> transitions;  // 状态转换规则
};
3-2-1 核心变量
  • 我们先来看最核心的一些定义
public:
	using State = std::string;
    using Event = std::string;
    using StateHandler = std::function<void()>;
private:
    State currentState;
    std::map<State, StateHandler> stateHandlers;  // 状态与处理函数的映射
    std::map<std::pair<State, Event>, State> transitions;  // 状态转换规则
  • State 表示状态机中的状态,这里设计为字符串类型(如 “Idle”, “Working”, “Paused”)。
  • Event 表示触发状态变化的事件,这里也设计为字符串类型(如 “Start”, “Pause”, “Resume”, “Stop”)。
  • StateHandler 是一个 std::function<void()> 类型,表示每个状态下要执行的具体操作
  • State currentState: 当前状态。该变量保存状态机的当前状态。
  • std::map<State, StateHandler> stateHandlers: 这是一个映射表,它将每个状态与一个状态处理函数(StateHandler)关联。
  • std::map<std::pair<State, Event>, State> transitions: 这是一个映射表,它将当前状态和触发事件的组合映射到目标状态。每当触发某个事件时,状态机会根据当前状态和事件,查找目标状态并进行状态转移。

3-2-2 函数
// 添加状态和相应的处理函数
    void AddState(const State& state, StateHandler handler) {
        stateHandlers[state] = handler;
    }

    // 添加事件和相应的状态转换
    void AddTransition(const State& fromState, const Event& event, const State& toState) {
        transitions[{fromState, event}] = toState;
    }

    // 触发事件,并根据当前状态和事件进行状态转换
    void TriggerEvent(const Event& event) {
        auto transitionKey = std::make_pair(currentState, event);
        if (transitions.find(transitionKey) != transitions.end()) {
            // 转到新的状态
            currentState = transitions[transitionKey];
            std::cout << "Transitioning to state: " << currentState << std::endl;
            // 执行新的状态的处理函数
            stateHandlers[currentState]();
        } else {
            std::cout << "No transition available for event: " << event << " from state: " << currentState << std::endl;
        }
    }

    // 设置初始状态
    void SetInitialState(const State& state) {
        currentState = state;
    }
  • AddState: 向状态机中添加状态和与之关联的处理函数(比如当状态进入时该做什么)。
  • AddTransition: 定义一个状态转移规则,指定在某个状态下触发某个事件时,状态机应该转到哪个新状态。
  • TriggerEvent: 根据当前状态和触发的事件,检查是否有相应的状态转移规则,如果有,则转换到目标状态,并执行目标状态的处理函数。
3-2-3 整体逻辑:
  • 初始状态通过 SetInitialState 设置。
  • 当调用 TriggerEvent 时,状态机会根据当前状态和触发的事件,查找 transitions 中的对应转移规则。
  • 如果找到了转移规则,状态机会转换到目标状态,并执行该状态的处理函数(例如打印 “Currently in Working state”)。

3-3 测试
  • 我们来进行下述状态和事件的测试
  • 我们假设有一个游戏:
    • State有 “Idle”, “Working”, “Paused”
    • Event有Start", “Pause”, “Resume”, “Stop”
    • 同时我们有下述转移表:
Start
Pause
Resume
Stop
Currently in Idle state
Currently in Working state
Currently in Paused state
  • 同时我们给每个状态分配好各自对应需要触发的函数
// 状态机的各个状态处理函数  
void IdleState() {  
    std::cout << "Currently in Idle state." << std::endl;  
}  
  
void WorkingState() {  
    std::cout << "Currently in Working state." << std::endl;  
}  
  
void PausedState() {  
    std::cout << "Currently in Paused state." << std::endl;  
}
  • 那么我们库这样进行构建:
  
int main() {  
    StateMachine sm;  
  
    // 添加状态和状态处理函数  
    sm.AddState("Idle", IdleState);  
    sm.AddState("Working", WorkingState);  
    sm.AddState("Paused", PausedState);  
  
    // 设置初始状态  
    sm.SetInitialState("Idle");  
  
    // 添加状态转换规则  
    sm.AddTransition("Idle", "Start", "Working");  
    sm.AddTransition("Working", "Pause", "Paused");  
    sm.AddTransition("Paused", "Resume", "Working");  
    sm.AddTransition("Working", "Stop", "Idle");  
  
    // 触发事件  
    sm.TriggerEvent("Start");  // 转到 Working 状态  
    sm.TriggerEvent("Pause");  // 转到 Paused 状态  
    sm.TriggerEvent("Resume"); // 转到 Working 状态  
    sm.TriggerEvent("Stop");   // 转到 Idle 状态  
  
    return 0;  
}
3-3-1 完整代码如下
#include <iostream>  
#include <functional>  
#include <map>  
#include <string>  
  
// 状态机类  
class StateMachine {  
public:  
    using State = std::string;  
    using Event = std::string;  
    using StateHandler = std::function<void()>;  
  
    // 添加状态和相应的处理函数  
    void AddState(const State& state, StateHandler handler) {  
        stateHandlers[state] = handler;  
    }  
  
    // 添加事件和相应的状态转换  
    void AddTransition(const State& fromState, const Event& event, const State& toState) {  
        transitions[{fromState, event}] = toState;  
    }  
  
    // 触发事件,并根据当前状态和事件进行状态转换  
    void TriggerEvent(const Event& event) {  
        auto transitionKey = std::make_pair(currentState, event);  
        if (transitions.find(transitionKey) != transitions.end()) {  
            // 转到新的状态  
            currentState = transitions[transitionKey];  
            std::cout << "Transitioning to state: " << currentState << std::endl;  
            // 执行新的状态的处理函数  
            stateHandlers[currentState]();  
        } else {  
            std::cout << "No transition available for event: " << event << " from state: " << currentState << std::endl;  
        }  
    }  
  
    // 设置初始状态  
    void SetInitialState(const State& state) {  
        currentState = state;  
    }  
  
private:  
    State currentState;  
    std::map<State, StateHandler> stateHandlers;  // 状态与处理函数的映射  
    std::map<std::pair<State, Event>, State> transitions;  // 状态转换规则  
};  
  
// 状态机的各个状态处理函数  
void IdleState() {  
    std::cout << "Currently in Idle state." << std::endl;  
}  
  
void WorkingState() {  
    std::cout << "Currently in Working state." << std::endl;  
}  
  
void PausedState() {  
    std::cout << "Currently in Paused state." << std::endl;  
}  
  
int main() {  
    StateMachine sm;  
  
    // 添加状态和状态处理函数  
    sm.AddState("Idle", IdleState);  
    sm.AddState("Working", WorkingState);  
    sm.AddState("Paused", PausedState);  
  
    // 设置初始状态  
    sm.SetInitialState("Idle");  
  
    // 添加状态转换规则  
    sm.AddTransition("Idle", "Start", "Working");  
    sm.AddTransition("Working", "Pause", "Paused");  
    sm.AddTransition("Paused", "Resume", "Working");  
    sm.AddTransition("Working", "Stop", "Idle");  
  
    // 触发事件  
    sm.TriggerEvent("Start");  // 转到 Working 状态  
    sm.TriggerEvent("Pause");  // 转到 Paused 状态  
    sm.TriggerEvent("Resume"); // 转到 Working 状态  
    sm.TriggerEvent("Stop");   // 转到 Idle 状态  
  
    return 0;  
}
  • 请添加图片描述
Start
Pause
Resume
Stop
Currently in Idle state
Currently in Working state
Currently in Paused state
  • 可以观察到,通过事件和委托可以很方便地管理不同的状态和状态之间的转移,同时让状态机的行为更加清晰和直观。



4 总结

  • 本文我们 基于C++委托事件实现多话题的发布-订阅模式,同时也实现了 基于委托和事件实现状态机。
  • 如有错误,欢迎指出!!!
  • 感谢大家的支持!!!