【问题标题】:C++ Callbacks? Should I use Member Func Pointers/Delegates/Events?C++ 回调?我应该使用成员函数指针/委托/事件吗?
【发布时间】:2011-05-15 04:57:16
【问题描述】:

我正在进入一个对我来说很陌生的领域,但基本上我需要在 C++ 中实现回调。我正在为自己设计一个工具包来简化我的生活。基本上它是一个 .dll 插件,它将向我的其他 .dll 插件公开很多功能。

其中一个函数是 HookEvent(const char *event_name, void *callback),它允许我挂钩被触发的不同事件。这是一个例子......

Example_Plugin1.dll 执行 HookEvent("player_spawn", &Plugin1::Event_PlayerSpawn); Example_Plugin2.dll 执行 HookEvent("player_spawn", &Plugin2::Event_PlayerSpawn);

我需要找出最好的(最好是最简单的)方法来设置一个可以很好地解决这个问题的回调系统。我已经阅读 C++ 回调几个小时了,发现了很多不同的方法。

我认为最简单的方法是制作一个模板,然后使用 typedef bool (ClassName::*EventHookCallback)(IGameEvent, bool);在那之后,我有点迷茫。

我还了解到委托或 .NET 样式的事件系统是其他可能的方法。我已经有些糊涂了,所以我不想再糊涂了,但觉得值得一问。

这是我正在阅读的 C++ .NET 样式事件系统的链接。 http://cratonica.wordpress.com/2010/02/19/implementing-c-net-events-in-c/

那你们有什么建议呢?任何关于实施它的提示将不胜感激。

【问题讨论】:

    标签: c++ events delegates callback handler


    【解决方案1】:

    我会使用抽象基类作为插件接口。 (事实上​​,我以前使用过类似下面的模式。)

    库,PluginIfc.h:

    class PluginIfc {
    public:
      virtual ~PluginIfc() = 0;
      virtual bool EventCallback(const char* event_name, IGameEvent, bool) = 0;     
    };
    
    // For Windows, add dllexport/dllimport magic to this declaration.
    // This is the only symbol you will look up from the plugin and invoke.
    extern "C" PluginIfc* GetPlugin();
    

    插件:

    #include <PluginIfc.h>
    class Plugin1 : public PluginIfc {
    public:
      virtual bool EventCallback(const char* event_name, IGameEvent, bool);
      Plugin1& get() { return the_plugin_obj; }
    
      bool Event_PlayerSpawn(IGameEvent, bool);
      // ...
    private:
      std::vector<std::string> _some_member;
    
      static Plugin1 the_plugin_obj; // constructed when plugin loaded
    };
    
    Plugin1 Plugin1::the_plugin_obj;
    PluginIfc* GetPlugin() { return &Plugin1::get(); }
    

    这样,您的插件类可以轻松拥有成员,并且 C++ 的虚拟调用机制会在 EventCallback 中为您提供良好的 this 指针。

    为每个事件类型创建一个虚拟方法可能很诱人,比如让Event_PlayerSpawn 和类似的方法成为虚拟方法。但是,每当您想添加事件类型时,如果这意味着更改类PluginIfc,您的旧编译插件将不再兼容。因此,使用字符串事件标识符(为了可扩展性)并让主回调将事件分类到更具体的方法会更安全。

    这里的主要缺点(与信号槽类型实现相比)是所有回调必须采用相同的参数集。但你的问题听起来就足够了。通过确保参数集非常灵活,使用要解析的字符串或Any 样式的对象,通常可以在该限制内工作。

    【讨论】:

      【解决方案2】:

      实现您自己的回调系统并非易事。
      我的理解是,您的目标是将事件类型映射到特定的回调函数。
      例如。如果“player_spawn”事件发生,将调用 &Plugin1::Event_PlayerSpawn。
      因此,您应该执行以下操作:
      1) 定义所有感兴趣的事件。使它们尽可能通用。他们能 封装您需要的任何信息
      2) 创建注册商。 IE。所有模块都注册他们对特定的兴趣的类 方法。例如。 Registrar.register(player_spawn,this,Event_PlayerSpawn);
      3) Registrar 拥有所有订阅者的队列。
      4) 您还可以为模块提供统一的接口。 IE。所有模块都实现了一个特定的功能,但根据事件的数据可以做不同的事情
      5) 当一个事件发生时,所有对特定事件感兴趣的订阅者都会通过调用相应的函数得到通知
      6)订阅者可以在需要时取消注册
      希望这会有所帮助。

      【讨论】:

        【解决方案3】:

        如果您想要通用事件触发 Boost.Signals2 可能适用。

        Boost.Signals2 库是一个 受管信号的实现 和插槽系统。信号代表 具有多个目标的回调,以及 也称为发布者或事件 在类似的系统中。信号是 连接到一组插槽,其中 是回调接收者(也称为 事件目标或订阅者),其中 当信号出现时被调用 “发射。”

        即使您不需要这种级别的灵活性,您也应该能够使用 Boost.Bind 或 C++0x 等效项来简化代码中的函数绑定。

        编辑: Herb Sutter 对您可能面临的问题进行了精彩的讨论 here。如果您决定不需要完整的 Boost 功能集,则可以将其用作指导,然后自行开发。

        【讨论】:

        • 如果可能的话,我有点希望在不使用 Boost 或其他额外库的情况下做到这一点。与仅使用成员函数指针或委托相比,这真的会简单得多吗?
        • 如果您需要简单的偶尔回调,那么这是矫枉过正。对于更完整的观察者模式实现,这将很有帮助。成本/收益决定取决于您的预期需求。如果您的编译器中有 C++0x,您甚至可以将“委托”设为 lambda(本地匿名函数)。看看这个:uint32t.blogspot.com/2009/05/…
        • 我其实很喜欢这样,但是我想我会选择这个系统,因为它似乎是迄今为止我所见过的最容易理解的系统。 cratonica.wordpress.com/2010/02/19/…
        • 好的,布雷特。 fwiw 我不喜欢依赖于非平凡预处理器宏的代码,因为它会使调试代码变得比应有的更难。但是,如果您的系统足够简单并且此代码“正常工作”,那应该没关系。
        【解决方案4】:

        Boost.Signals 是我的选择,结合 boost::bindBoost.Function 之类的东西。

        【讨论】:

          【解决方案5】:

          听起来您可能对如何构建自己的插件框架感兴趣。您将遇到的问题可能是相同的。看看这篇精彩的 Dobbs 博士文章Building Your Own Plugin Framework

          希望这会有所帮助!

          【讨论】:

          • 虽然看起来很有趣,但我相信这对于我的需求来说过于复杂了。正如我所提到的,我已经在使用 .dll 插件来创建这些函数/将这些函数公开给我的其他插件,并且我正在使用的 API 提供了简单的方法来做到这一点。
          • 听起来你知道你需要什么。祝你好运!
          【解决方案6】:

          使用 Qt Signal 和 Slot 怎么样?它可以做回调所做的事情,但不会让任何不属于你的回调参数的东西成为全局的。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2011-12-24
            • 1970-01-01
            • 2013-01-26
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2010-12-01
            相关资源
            最近更新 更多