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

打印 上一主题 下一主题

主题 867|帖子 867|积分 2601

前言



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

    • 上:【C++委托与事故】函数指针,回调机制,事故式编程与松耦合的计划模式(上)


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

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

  • 本期本系列下半我们来谈谈:

    • 基于C++委托和事故实现 发布-订阅模式`
    • 模仿ROS2进行多话题发布-订阅模式的编写
    • 基于C++委托和事故实现状态机


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

1-1 概念



  • 发布-订阅模式(Publisher-Subscriber Pattern),是一种常见的行为型计划模式,也被称为观察者模式的变种。这种模式通过将事故的发布和吸收解耦合,让一个系统中的多个组件之间的交互变得更加机动,尤其适用于消息传递和事故处理场景。
  • 发布-订阅模式的主要构成部分:

    • Publisher(发布者):发布消息的实体,通常不直接知道有谁订阅了它的事故。
    • Subscriber(订阅者):订阅事故并吸收消息的实体。订阅者对某个特定事故感兴趣,会注册自己到事故总线中,等待事故的发生。
    • EventBus(事故总线):作为事故的传递通道,负责和谐发布者和订阅者之间的消息传递。它可以存储订阅者的回调函数,并在发布者触发事故时,关照全部订阅该事故的订阅者。

1-2 发布-订阅模式观察者模式的区别



  • 一句话概括就是:发布-订阅模式由第三方来管理发布和订阅,观察者模式是直接由发布者管理
特性观察者模式(Observer Pattern)发布-订阅模式(Publisher-Subscriber Pattern)模式类型行为型计划模式行为型计划模式耦合度观察者和主题(发布者)之间存在直接依赖关系,观察者直接注册到主题上发布者与订阅者之间解耦,发布者不直接知道谁订阅了它的事故关照机制主题对象(Subject)直接关照其全部观察者(Observers)通过事故总线或消息队列关照订阅者,发布者不直接关照订阅者发布者与订阅者的关系发布者(主题)知道全部的观察者(订阅者)并维护它们发布者与订阅者之间没有直接联系,事故总线或消息中介负责事故传递订阅机制观察者通过直接注册到主题上进行订阅订阅者通过事故总线或消息队列进行订阅扩展性扩展性较差,增长新的观察者可能必要修改主题(发布者)扩展性强,可以随时增长新的订阅者或事故类型,且无需修改发布者事故传播方式主题触发时直接传递给观察者通过中介(事故总线、消息队列等)分发给订阅者事故流向一对多,主题和全部观察者有直接的联系多对多,发布者与多个订阅者通过事故总线解耦 1-3 UML类图

     1-4 代码示例:基于C++委托和事故实现 发布-订阅模式



  • 根据上述的概念,我们起首先来编写事故总线 类,他必要包罗两种方法:

    • 发布方法:关照全部订阅者
    • 订阅方法:添加到订阅队列
    • 一个事故,这里我定义回调函数的函数签名为void(const std::string&),并使用std::function来管理

  • 那么我们有:
  1. #include <iostream>
  2. #include <string>
  3. #include <vector>
  4. #include <functional>
  5. #include <algorithm>
  6. // 定义一个事件总线类
  7. class EventBus {
  8. public:
  9.     // 使用 std::function 来存储订阅者的回调函数
  10.     using EventCallback = std::function<void(const std::string&)>;
  11.     // 订阅事件,传入一个回调函数
  12.     void subscribe(EventCallback callback) {
  13.         subscribers.push_back(callback);
  14.     }
  15.     // 发布事件,通知所有订阅者
  16.     void publish(const std::string& event) {
  17.         std::cout << "Event published: " << event << std::endl;
  18.         for (auto& subscriber : subscribers) {
  19.             subscriber(event);  // 通知每个订阅者
  20.         }
  21.     }
  22. private:
  23.     std::vector<EventCallback> subscribers;  // 存储所有订阅者的回调函数
  24. };
复制代码


  • 紧接着我们来编写发布者和订阅者
  1. // 发布者类
  2. class Publisher {
  3. public:
  4.     Publisher(EventBus& eventBus) : eventBus(eventBus) {}
  5.     // 发布事件
  6.     void publishEvent(const std::string& event) {
  7.         eventBus.publish(event);
  8.     }
  9. private:
  10.     EventBus& eventBus;  // 引用事件总线
  11. };
  12. // 订阅者类
  13. class Subscriber {
  14. public:
  15.     Subscriber(const std::string& name) : name(name) {}
  16.     // 当事件发布时,订阅者的回调函数
  17.     void onEventReceived(const std::string& event) {
  18.         std::cout << name << " received event: " << event << std::endl;
  19.     }
  20. private:
  21.     std::string name;  // 订阅者的名字
  22. };
复制代码


  • 完备代码:
  1. #include <iostream>  
  2. #include <string>  
  3. #include <vector>  
  4. #include <functional>  
  5. #include <algorithm>  
  6.   
  7. // 定义一个事件总线类  
  8. class EventBus {  
  9. public:  
  10.     // 使用 std::function 来存储订阅者的回调函数  
  11.     using EventCallback = std::function<void(const std::string&)>;  
  12.   
  13.     // 订阅事件,传入一个回调函数  
  14.     void subscribe(EventCallback callback) {  
  15.         subscribers.push_back(callback);  
  16.     }  
  17.   
  18.     // 发布事件,通知所有订阅者  
  19.     void publish(const std::string& event) {  
  20.         std::cout << "Event published: " << event << std::endl;  
  21.         for (auto& subscriber : subscribers) {  
  22.             subscriber(event);  // 通知每个订阅者  
  23.         }  
  24.     }  
  25.   
  26. private:  
  27.     std::vector<EventCallback> subscribers;  // 存储所有订阅者的回调函数  
  28. };  
  29. // 发布者类  
  30. class Publisher {  
  31. public:  
  32.     Publisher(EventBus& eventBus) : eventBus(eventBus) {}  
  33.   
  34.     // 发布事件  
  35.     void publishEvent(const std::string& event) {  
  36.         eventBus.publish(event);  
  37.     }  
  38.   
  39. private:  
  40.     EventBus& eventBus;  // 引用事件总线  
  41. };  
  42.   
  43. // 订阅者类  
  44. class Subscriber {  
  45. public:  
  46.     Subscriber(const std::string& name) : name(name) {}  
  47.   
  48.     // 当事件发布时,订阅者的回调函数  
  49.     void onEventReceived(const std::string& event) {  
  50.         std::cout << name << " received event: " << event << std::endl;  
  51.     }  
  52.   
  53. private:  
  54.     std::string name;  // 订阅者的名字  
  55. };  
  56. int main() {  
  57.     // 创建事件总线  
  58.     EventBus eventBus;  
  59.   
  60.     // 创建发布者,传入事件总线  
  61.     Publisher publisher(eventBus);  
  62.   
  63.     // 创建订阅者  
  64.     Subscriber subscriber1("Subscriber 1");  
  65.     Subscriber subscriber2("Subscriber 2");  
  66.   
  67.     // 订阅事件  
  68.     eventBus.subscribe(std::bind(&Subscriber::onEventReceived,&subscriber1,std::placeholders::_1));  
  69.     eventBus.subscribe(std::bind(&Subscriber::onEventReceived,&subscriber2,std::placeholders::_1));  
  70.   
  71.     // 发布一个事件  
  72.     publisher.publishEvent("New Article Published");  
  73.   
  74.     return 0;  
  75. }
复制代码


  • 同样这里我们绑定类内函数使用std::bind(上一期讲过了的)
  1. eventBus.subscribe(std::bind(&Subscriber::onEventReceived,&subscriber1,std::placeholders::_1));
复制代码


  • 可以看到,发布者调用总线的发布方法,并不必要知道全部的订阅者就能关照全部订阅者




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

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

2-1 ROS的发布订阅模型(扩展)



  • 打仗过ROS和ROS2的朋侪一定不陌生,在 ROS2 中,发布者和订阅者通过 话题(Topic) 来进行通讯。我们通过创建 Publisher(发布者)Subscriber(订阅者) 节点来实现消息的发布和吸收。
  • 这里我们来看ROS2的一个底子的发布订阅代码:
  • 发布方
  1. #include "rclcpp/rclcpp.hpp"
  2. #include "std_msgs/msg/string.hpp"
  3. using std::placeholders::_1;
  4. class MinimalPublisher : public rclcpp::Node
  5. {
  6. public:
  7.     MinimalPublisher() : Node("minimal_publisher")
  8.     {
  9.         publisher_ = this->create_publisher<std_msgs::msg::String>("topic", 10);
  10.         timer_ = this->create_wall_timer(
  11.             std::chrono::seconds(1),
  12.             std::bind(&MinimalPublisher::timer_callback, this)
  13.         );
  14.     }
  15. private:
  16.     void timer_callback()
  17.     {
  18.         auto message = std_msgs::msg::String();
  19.         message.data = "Hello, ROS2!";
  20.         RCLCPP_INFO(this->get_logger(), "Publishing: '%s'", message.data.c_str());
  21.         publisher_->publish(message);
  22.     }
  23.     rclcpp::Publisher<std_msgs::msg::String>::SharedPtr publisher_;
  24.     rclcpp::TimerBase::SharedPtr timer_;
  25. };
  26. int main(int argc, char ** argv)
  27. {
  28.     rclcpp::init(argc, argv);
  29.     rclcpp::spin(std::make_shared<MinimalPublisher>());
  30.     rclcpp::shutdown();
  31.     return 0;
  32. }
复制代码


  • 吸收方:
  1. #include "rclcpp/rclcpp.hpp"
  2. #include "std_msgs/msg/string.hpp"
  3. class MinimalSubscriber : public rclcpp::Node
  4. {
  5. public:
  6.     MinimalSubscriber() : Node("minimal_subscriber")
  7.     {
  8.         subscription_ = this->create_subscription<std_msgs::msg::String>(
  9.             "topic", 10, std::bind(&MinimalSubscriber::topic_callback, this, _1)
  10.         );
  11.     }
  12. private:
  13.     void topic_callback(const std_msgs::msg::String::SharedPtr msg) const
  14.     {
  15.         RCLCPP_INFO(this->get_logger(), "Received: '%s'", msg->data.c_str());
  16.     }
  17.     rclcpp::Subscription<std_msgs::msg::String>::SharedPtr subscription_;
  18. };
  19. int main(int argc, char ** argv)
  20. {
  21.     rclcpp::init(argc, argv);
  22.     rclcpp::spin(std::make_shared<MinimalSubscriber>());
  23.     rclcpp::shutdown();
  24.     return 0;
  25. }
复制代码


  • 没有打仗过的朋侪也没关系,这里说明一下:
  • 发布者(Publisher):在 ROS2 中,发布者通过 create_publisher 函数来创建一个发布者,并将其与特定的 话题(topic)相干联。

    • 这里发布者注册了一个名为"topic"的话题
    • 同时这里指定了事故(回调函数)的函数类型为void(*)(std_msgs::msg::String)

  1. publisher_ = this->create_publisher<std_msgs::msg::String>("topic", 10);
复制代码


  • 也就是当发布者进行发布的时间,会关照全部订阅了"topic"这个话题的全部订阅者
  1. publisher_->publish(message);
复制代码


  • 订阅者(Subscriber):订阅者通过 create_subscription 函数来创建一个订阅者,并指==定订阅的 话题(topic)==以及回调函数。在这个例子中,订阅者会吸收到来自觉布者发布的消息,并通过回调函数打印消息内容。

    • 同理这里我们订阅方注册了话题为"topic"
    • 并绑定了当发布者进行更新的时间必要执行的事故(回调函数)topic_callback

  1. subscription_ = this->create_subscription<std_msgs::msg::String>(
  2.             "topic", 10, std::bind(&MinimalSubscriber::topic_callback, this, _1)
  3.         );
复制代码


  • 不难发现,当出现多组多对多不同话题进行通讯的时间,单纯的观察者模式就会宕机,为此发布订阅模式把这个处理逻辑递交给事故管理总线,在上述例子中也就是rclcpp内部实现的。
2-2 实现话题-发布订阅模型(进阶):实现发布者类和订阅者类



  • 那么同样道理,我们可以之间使用C++的委托和事故实现上述功能,同时我们借助模板,仿造类型的发布订阅逻辑
2-2-1 前向声明 EventBus



  • 由于后续的Subscriber 类必要引用 EventBus 类型的指针或引用,而编译器必要知道 EventBus 类型。以是我们必要先进行前向声明总线类,外貌传递的是MsgType
  1. // 订阅者类  
  2. template <typename MsgType>  
  3. class EventBus;
复制代码
2-2-2 Publisher 类



  • 这次我们先创建发布者类

    • 发布者的创建必要指定事故总线类EventBus和话题
    • 发布者类的发布函数调用事故总线类EventBus的发布函数
    • 这里我们使用智能指针进行管理

      • 关于智能指针可以看之前写的一篇文章 【RAII | 计划模式】C++智能指针,内存管理与计划模式


  1. // 发布者类  
  2. template <typename MsgType>  
  3. class Publisher  
  4. {  
  5. public:  
  6.     Publisher(std::shared_ptr<EventBus<MsgType>> bus, const std::string &topic)  
  7.             : eventBus(bus), topic(topic) {}  
  8.   
  9.     // 发布消息到事件总线  
  10.     void publish(const MsgType &message)  
  11.     {  
  12.         eventBus->publish(topic, message);  // 将消息发布到特定的话题  
  13.     }  
  14.   
  15. private:  
  16.     std::shared_ptr<EventBus<MsgType>> eventBus;  // 引用事件总线  
  17.     std::string topic;                            // 发布者的主题  
  18. };
复制代码
2-2-3 Subscriber 类



  • 同样道理我们指定订阅者

    • Subscriber 是一个模板类,允许我们为任意类型的消息(MsgType)创建订阅者。
    • 在构造时,Subscriber 必要提供一个名字和一个回调函数。回调函数是一个 std::function 对象,用来处理吸收到的消息。
    • onMessageReceived:当事故总线发布消息时,Subscriber 会调用这个函数来执行它的回调函数。

  1. template <typename MsgType>  
  2. class Subscriber  
  3. {  
  4. public:  
  5.     Subscriber(const std::string &name, std::function<void(const MsgType &)> callback)  
  6.             : subscriberName(name), callbackFn(callback) {}  
  7.   
  8.     // 当接收到消息时调用回调函数  
  9.     void onMessageReceived(const MsgType &message)  
  10.     {  
  11.         callbackFn(message);  
  12.     }  
  13.   
  14. private:  
  15.     std::string subscriberName;  
  16.     std::function<void(const MsgType &)> callbackFn;  // 回调函数  
  17. };
复制代码

2-3 实现话题-发布订阅模型(进阶):实现事故总线类EventBus



  • 紧接着我们来看最为重要的事故总线类EventBus,先放上完备的代码,然后我们详细分析
  1.   
  2. // 事件总线类  
  3. template <typename MsgType>  
  4. class EventBus : public std::enable_shared_from_this<EventBus<MsgType>> // 继承 enable_shared_from_this{  
  5. public:  
  6.     using MessageType = MsgType;  
  7.     using CallbackType = std::function<void(const MessageType &)>;  
  8.   
  9.     // 创建订阅者并将其添加到话题订阅者列表中  
  10.     std::shared_ptr<Subscriber<MsgType>> create_subscription(const std::string &topic, CallbackType callback)  
  11.     {  
  12.         auto subscriber = std::make_shared<Subscriber<MsgType>>(topic, callback);  
  13.         subscribers[topic].push_back(subscriber);  // 将订阅者添加到该话题的订阅者列表中  
  14.         return subscriber;  
  15.     }  
  16.   
  17.     // 创建发布者  
  18.     std::shared_ptr<Publisher<MsgType>> create_publisher(const std::string &topic)  
  19.     {  
  20.         return std::make_shared<Publisher<MsgType>>(this->shared_from_this(), topic);  
  21.     }  
  22.   
  23.     // 发布消息到指定话题  
  24.     void publish(const std::string &topic, const MsgType &message)  
  25.     {  
  26.         auto it = subscribers.find(topic);  
  27.         if (it != subscribers.end())  // 检查该话题是否有订阅者  
  28.         {  
  29.             for (const auto &subscriber : it->second)  
  30.             {  
  31.                 subscriber->onMessageReceived(message);  // 调用订阅者的回调函数  
  32.             }  
  33.         }  
  34.         else  
  35.         {  
  36.             std::cout << "No subscribers for topic: " << topic << std::endl;  
  37.         }  
  38.     }  
  39.   
  40. private:  
  41.     std::unordered_map<std::string, std::vector<std::shared_ptr<Subscriber<MsgType>>>> subscribers;  // 存储话题和其订阅者  
  42. };
复制代码
2-3-1 使用shared_from_this()传递给发布者类



  • 我们回首一下上面发布者类中必要事故类来调用事故总线类EventBus的publish方法
  1. // 发布消息到事件总线  
  2. void publish(const MsgType &message)  
  3. {  
  4.         eventBus->publish(topic, message);  // 将消息发布到特定的话题  
  5. }  
复制代码


  • 因此在事故总线类EventBus中我们必要被本体的智能指针传递给发布者类
  1. // 创建发布者  
  2. std::shared_ptr<Publisher<MsgType>> create_publisher(const std::string &topic)  
  3. {  
  4.         return std::make_shared<Publisher<MsgType>>(this->shared_from_this(), topic);  
  5. }
复制代码


  • 在智能指针章节我们提到过,继承 std::enable_shared_from_this<EventBus<MsgType>>允许对象通过 shared_from_this() 得到一个指向自己对象的 shared_ptr。

  • 发布者类就可以使用this->shared_from_this()传递事故总线类EventBus
  1. // 创建发布者  
  2.     std::shared_ptr<Publisher<MsgType>> create_publisher(const std::string &topic)  
  3.     {  
  4.         return std::make_shared<Publisher<MsgType>>(this->shared_from_this(), topic);  
  5.     }  
复制代码
2-3-2 订阅者类创建与事故存储



  • 根据我们的定义,我们定义事故为如下
  1. using MessageType = MsgType;  
  2. using CallbackType = std::function<void(const MessageType &)>;  
复制代码


  • 同时我们维护一个映射函数,用于存储话题和函数的对应关系
  1. std::unordered_map<std::string, std::vector<std::shared_ptr<Subscriber<MsgType>>>> subscribers;  // 存储话题和其订阅者  
复制代码


  • 那么每次我们创建订阅者就只必要添加到对应的容器中
  1. // 创建订阅者并将其添加到话题订阅者列表中  
  2. std::shared_ptr<Subscriber<MsgType>> create_subscription(const std::string &topic, CallbackType callback)  
  3. {  
  4.         auto subscriber = std::make_shared<Subscriber<MsgType>>(topic, callback);  
  5.         subscribers[topic].push_back(subscriber);  // 将订阅者添加到该话题的订阅者列表中  
  6.         return subscriber;  
  7. }  
复制代码
2-3-3 完备代码与输出



  • 完备代码如下
  1. #include <iostream>  #include <unordered_map>  #include <vector>  #include <functional>  #include <memory>  #include <string>    // 订阅者类  
  2. template <typename MsgType>  
  3. class EventBus;
  4. // Forward declaration for EventBus    template <typename MsgType>  
  5. class Subscriber  
  6. {  
  7. public:  
  8.     Subscriber(const std::string &name, std::function<void(const MsgType &)> callback)  
  9.             : subscriberName(name), callbackFn(callback) {}  
  10.   
  11.     // 当接收到消息时调用回调函数  
  12.     void onMessageReceived(const MsgType &message)  
  13.     {  
  14.         callbackFn(message);  
  15.     }  
  16.   
  17. private:  
  18.     std::string subscriberName;  
  19.     std::function<void(const MsgType &)> callbackFn;  // 回调函数  
  20. };
  21.     // 发布者类  
  22. template <typename MsgType>  
  23. class Publisher  
  24. {  
  25. public:  
  26.     Publisher(std::shared_ptr<EventBus<MsgType>> bus, const std::string &topic)  
  27.             : eventBus(bus), topic(topic) {}  
  28.   
  29.     // 发布消息到事件总线  
  30.     void publish(const MsgType &message)  
  31.     {  
  32.         eventBus->publish(topic, message);  // 将消息发布到特定的话题  
  33.     }  
  34.   
  35. private:  
  36.     std::shared_ptr<EventBus<MsgType>> eventBus;  // 引用事件总线  
  37.     std::string topic;                            // 发布者的主题  
  38. };
  39.    
  40. // 事件总线类  
  41. template <typename MsgType>  
  42. class EventBus : public std::enable_shared_from_this<EventBus<MsgType>> // 继承 enable_shared_from_this{  
  43. public:  
  44.     using MessageType = MsgType;  
  45.     using CallbackType = std::function<void(const MessageType &)>;  
  46.   
  47.     // 创建订阅者并将其添加到话题订阅者列表中  
  48.     std::shared_ptr<Subscriber<MsgType>> create_subscription(const std::string &topic, CallbackType callback)  
  49.     {  
  50.         auto subscriber = std::make_shared<Subscriber<MsgType>>(topic, callback);  
  51.         subscribers[topic].push_back(subscriber);  // 将订阅者添加到该话题的订阅者列表中  
  52.         return subscriber;  
  53.     }  
  54.   
  55.     // 创建发布者  
  56.     std::shared_ptr<Publisher<MsgType>> create_publisher(const std::string &topic)  
  57.     {  
  58.         return std::make_shared<Publisher<MsgType>>(this->shared_from_this(), topic);  
  59.     }  
  60.   
  61.     // 发布消息到指定话题  
  62.     void publish(const std::string &topic, const MsgType &message)  
  63.     {  
  64.         auto it = subscribers.find(topic);  
  65.         if (it != subscribers.end())  // 检查该话题是否有订阅者  
  66.         {  
  67.             for (const auto &subscriber : it->second)  
  68.             {  
  69.                 subscriber->onMessageReceived(message);  // 调用订阅者的回调函数  
  70.             }  
  71.         }  
  72.         else  
  73.         {  
  74.             std::cout << "No subscribers for topic: " << topic << std::endl;  
  75.         }  
  76.     }  
  77.   
  78. private:  
  79.     std::unordered_map<std::string, std::vector<std::shared_ptr<Subscriber<MsgType>>>> subscribers;  // 存储话题和其订阅者  
  80. };
  81.     // 回调函数  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 概念与计划



  • 我们之前在【C++决议和状态管理】从状态模式,有限状态机,行为树到决议树(二):从FSM开始的2D游戏角色操控底层源码编写提到过状态机

  • 我们知道状态机的根本原理是根据当前的状态和输入的事故,触发状态的转移。
  • 那么我们可以这样的把事故和委托的概念运用到者上面:

    • 使用 委托(通过 C++ 中的 std::function)处理状态转换时的行为。
    • 使用 事故(即触发某些行为的输入)来引发状态转移。
    • 同时我们创建一个状态机 类用于管理当前状态,并根据事故触发状态的转换。

3-2 代码编写



  • 我们编写如下状态机类代码:
  • 老例子先上代码然后细说
  1. #include <iostream>
  2. #include <functional>
  3. #include <map>
  4. #include <string>
  5. // 状态机类
  6. class StateMachine {
  7. public:
  8.     using State = std::string;
  9.     using Event = std::string;
  10.     using StateHandler = std::function<void()>;
  11.     // 添加状态和相应的处理函数
  12.     void AddState(const State& state, StateHandler handler) {
  13.         stateHandlers[state] = handler;
  14.     }
  15.     // 添加事件和相应的状态转换
  16.     void AddTransition(const State& fromState, const Event& event, const State& toState) {
  17.         transitions[{fromState, event}] = toState;
  18.     }
  19.     // 触发事件,并根据当前状态和事件进行状态转换
  20.     void TriggerEvent(const Event& event) {
  21.         auto transitionKey = std::make_pair(currentState, event);
  22.         if (transitions.find(transitionKey) != transitions.end()) {
  23.             // 转到新的状态
  24.             currentState = transitions[transitionKey];
  25.             std::cout << "Transitioning to state: " << currentState << std::endl;
  26.             // 执行新的状态的处理函数
  27.             stateHandlers[currentState]();
  28.         } else {
  29.             std::cout << "No transition available for event: " << event << " from state: " << currentState << std::endl;
  30.         }
  31.     }
  32.     // 设置初始状态
  33.     void SetInitialState(const State& state) {
  34.         currentState = state;
  35.     }
  36. private:
  37.     State currentState;
  38.     std::map<State, StateHandler> stateHandlers;  // 状态与处理函数的映射
  39.     std::map<std::pair<State, Event>, State> transitions;  // 状态转换规则
  40. };
复制代码
3-2-1 焦点变量



  • 我们先来看最焦点的一些定义
  1. public:
  2.         using State = std::string;
  3.     using Event = std::string;
  4.     using StateHandler = std::function<void()>;
  5. private:
  6.     State currentState;
  7.     std::map<State, StateHandler> stateHandlers;  // 状态与处理函数的映射
  8.     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 函数

  1. // 添加状态和相应的处理函数
  2.     void AddState(const State& state, StateHandler handler) {
  3.         stateHandlers[state] = handler;
  4.     }
  5.     // 添加事件和相应的状态转换
  6.     void AddTransition(const State& fromState, const Event& event, const State& toState) {
  7.         transitions[{fromState, event}] = toState;
  8.     }
  9.     // 触发事件,并根据当前状态和事件进行状态转换
  10.     void TriggerEvent(const Event& event) {
  11.         auto transitionKey = std::make_pair(currentState, event);
  12.         if (transitions.find(transitionKey) != transitions.end()) {
  13.             // 转到新的状态
  14.             currentState = transitions[transitionKey];
  15.             std::cout << "Transitioning to state: " << currentState << std::endl;
  16.             // 执行新的状态的处理函数
  17.             stateHandlers[currentState]();
  18.         } else {
  19.             std::cout << "No transition available for event: " << event << " from state: " << currentState << std::endl;
  20.         }
  21.     }
  22.     // 设置初始状态
  23.     void SetInitialState(const State& state) {
  24.         currentState = state;
  25.     }
复制代码


  • AddState: 向状态机中添加状态和与之关联的处理函数(比如当状态进入时该做什么)。


  • AddTransition: 定义一个状态转移规则,指定在某个状态下触发某个事故时,状态机应该转到哪个新状态。
  • TriggerEvent: 根据当前状态和触发的事故,检查是否有相应的状态转移规则,如果有,则转换到目的状态,并执行目的状态的处理函数。
3-2-3 整体逻辑:



  • 初始状态通过 SetInitialState 设置。


  • 当调用 TriggerEvent 时,状态机会根据当前状态和触发的事故,查找 transitions 中的对应转移规则。
  • 如果找到了转移规则,状态机会转换到目的状态,并执行该状态的处理函数(比方打印 “Currently in Working state”)。

3-3 测试



  • 我们来进行下述状态和事故的测试
  • 我们假设有一个游戏:

    • State有 “Idle”, “Working”, “Paused”
    • Event有Start", “Pause”, “Resume”, “Stop”
    • 同时我们有下述转移表:

     

  • 同时我们给每个状态分配好各自对应必要触发的函数
  1. // 状态机的各个状态处理函数  
  2. void IdleState() {  
  3.     std::cout << "Currently in Idle state." << std::endl;  
  4. }  
  5.   
  6. void WorkingState() {  
  7.     std::cout << "Currently in Working state." << std::endl;  
  8. }  
  9.   
  10. void PausedState() {  
  11.     std::cout << "Currently in Paused state." << std::endl;  
  12. }
复制代码


  • 那么我们库这样进行构建:
  1.   
  2. int main() {  
  3.     StateMachine sm;  
  4.   
  5.     // 添加状态和状态处理函数  
  6.     sm.AddState("Idle", IdleState);  
  7.     sm.AddState("Working", WorkingState);  
  8.     sm.AddState("Paused", PausedState);  
  9.   
  10.     // 设置初始状态  
  11.     sm.SetInitialState("Idle");  
  12.   
  13.     // 添加状态转换规则  
  14.     sm.AddTransition("Idle", "Start", "Working");  
  15.     sm.AddTransition("Working", "Pause", "Paused");  
  16.     sm.AddTransition("Paused", "Resume", "Working");  
  17.     sm.AddTransition("Working", "Stop", "Idle");  
  18.   
  19.     // 触发事件  
  20.     sm.TriggerEvent("Start");  // 转到 Working 状态  
  21.     sm.TriggerEvent("Pause");  // 转到 Paused 状态  
  22.     sm.TriggerEvent("Resume"); // 转到 Working 状态  
  23.     sm.TriggerEvent("Stop");   // 转到 Idle 状态  
  24.   
  25.     return 0;  
  26. }
复制代码
3-3-1 完备代码如下

  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;  // 状态转换规则  };    // 状态机的各个状态处理函数  
  2. void IdleState() {  
  3.     std::cout << "Currently in Idle state." << std::endl;  
  4. }  
  5.   
  6. void WorkingState() {  
  7.     std::cout << "Currently in Working state." << std::endl;  
  8. }  
  9.   
  10. void PausedState() {  
  11.     std::cout << "Currently in Paused state." << std::endl;  
  12. }
  13.    
  14. int main() {  
  15.     StateMachine sm;  
  16.   
  17.     // 添加状态和状态处理函数  
  18.     sm.AddState("Idle", IdleState);  
  19.     sm.AddState("Working", WorkingState);  
  20.     sm.AddState("Paused", PausedState);  
  21.   
  22.     // 设置初始状态  
  23.     sm.SetInitialState("Idle");  
  24.   
  25.     // 添加状态转换规则  
  26.     sm.AddTransition("Idle", "Start", "Working");  
  27.     sm.AddTransition("Working", "Pause", "Paused");  
  28.     sm.AddTransition("Paused", "Resume", "Working");  
  29.     sm.AddTransition("Working", "Stop", "Idle");  
  30.   
  31.     // 触发事件  
  32.     sm.TriggerEvent("Start");  // 转到 Working 状态  
  33.     sm.TriggerEvent("Pause");  // 转到 Paused 状态  
  34.     sm.TriggerEvent("Resume"); // 转到 Working 状态  
  35.     sm.TriggerEvent("Stop");   // 转到 Idle 状态  
  36.   
  37.     return 0;  
  38. }
复制代码



     

  • 可以观察到,通过事故和委托可以很方便地管理不同的状态和状态之间的转移,同时让状态机的行为更加清晰和直观。



4 总结



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

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

正序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

万有斥力

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表