C++:如何构建没有空指针的事件/消息系统?
我希望在我的 C++ 项目中拥有一个动态消息传递系统,其中有一个固定的现有事件列表,事件可以在运行时的任何地方触发,并且您可以在其中订阅某些事件的回调函数。
应该有一个选项可以在这些事件中传递参数。例如,一个事件可能不需要任何参数 (EVENT_EXIT)
,而有些事件可能需要多个 (EVENT_PLAYER_CHAT: 玩家对象指针,带有消息的字符串)
第一个选项这可能是允许在触发事件时将 void 指针作为参数传递给事件管理器,并在回调函数中接收它。
虽然:我被告知 void 指针是不安全的,我不应该使用它们。
- 如何在不使用 void 指针的情况下保留事件的(半)动态参数类型和计数?
I'd like to have a dynamic messaging system in my C++ project, one where there is a fixed list of existing events, events can be triggered anywhere during runtime, and where you can subscribe callback functions to certain events.
There should be an option for arguments passed around in those events. For example, one event might not need any arguments (EVENT_EXIT)
, and some may need multiple ones (EVENT_PLAYER_CHAT: Player object pointer, String with message)
The first option for making this possible is allowing to pass a void pointer as argument to the event manager when triggering an event, and receiving it in the callback function.
Although: I was told that void pointers are unsafe and I shouldn't use them.
- How can I keep (semi) dynamic argument types and counts for my events whilst not using void pointers?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(5)
由于其他人已经提到了访问者模式,这里使用 Boost 进行了轻微的改动.变体。当您需要一组基于值的不同行为时,这个库通常是一个不错的选择(或者至少对我来说是这样)。与
void*
相比,它具有静态类型检查的优点:如果您编写的访问者类错过了其中一种情况,您的代码将不会编译而不是失败在运行时。步骤 1: 定义消息类型:
步骤 2: 定义访问者:
这定义了一个事件处理程序,可以很好地分离每种事件的代码。所有
operator()
重载的存在都会在编译时(在模板实例化时)进行检查,因此如果您稍后添加事件类型,编译器将强制您添加相应的处理程序代码。请注意,
event_handler
是boost::static_visitor
的子类。这决定了每个operator()
重载的返回类型。第 3 步:使用事件处理程序:
这里,
apply_visitor
将为e
的“实际”值调用适当的重载。例如,如果我们定义get_event
如下:那么返回值将隐式转换为
event(EXIT_EVENT())
。然后apply_visitor
将调用相应的operator()(EXIT_EVENT const&)
重载。Since others have mentioned the visitor pattern, here is a slight twist using Boost.Variant. This library is often a good choice (or at least it has been for me) when you need a set of different behaviors based on a value. Compared to a
void*
, it has the benefit of static type checking: if you write a visitor class the misses one of the cases, your code will not compile rather than failing at run time.Step 1: Define message types:
Step 2: Define a visitor:
This defines an event handler that nicely separates out the code for each kind of event. The existence of all
operator()
overloads is checked at compile time (on template instantiation), so if you add an event type later, the compiler will force you to add corresponding handler code.Note that
event_handler
subclassesboost::static_visitor<void>
. This determines the return type for each of theoperator()
overloads.Step 3: Use your event handler:
Here,
apply_visitor
will call the appropriate overload for the 'actual' value ofe
. For example, if we defineget_event
as follows:Then the return value will be converted implicitly to
event(EXIT_EVENT())
. Thenapply_visitor
will call the correspondingoperator()(EXIT_EVENT const&)
overload.模板允许您编写类型安全的事件管理器,而无需先验了解消息类型。
如果事件类型在运行时发生变化,或者您需要将多种类型混合到一个容器中,则可以使用指向所有消息/事件类型的公共基类的指针。
Templates would allow you to write a type-safe event manager without it knowing the message types a-priori.
If the event types change at runtime, or you need to mix multiple types into a single container, you can use pointers to a common base class of all the message/event types.
我过去所做的事情是使用(优秀的)FastDelegate 库建立一个基于委托的系统,与 C# 中的系统不同:http://www.codeproject.com/Articles/7150/Member-Function-Pointers-and-the-Fastest-Possible
因此,有了这些,我创建了一些通用的事件类来包含委托列表,如下所示:
然后,您可以让各种子组件公开其特定的事件对象(我使用了接口样式,但这不是必需的):
该接口的使用者可以像这样注册:
结果是每个事件类型都是单独静态类型的——没有动态类型转换。然而,它确实分散了事件系统,这对于您的架构来说可能是也可能不是问题。
Something I've done in the past is set up a delegate-based system not unlike what is in C#, using the (excellent) FastDelegate library: http://www.codeproject.com/Articles/7150/Member-Function-Pointers-and-the-Fastest-Possible
So with that in hand, I created some general-purpose Event classes to contain the lists of delegates, like so:
Then you can have the various sub-components expose their specific event objects (I used an interface-style but that's not necessary):
Consumers of this interface can register like so:
The result is is all individually static-typed per event type--no dynamic_casting. However it does decentralize the event system, which may or may not be an issue for your architecture.
您可以使用基类(可选的抽象类)并使用
dynamic_cast
。该参数将在运行时进行检查。不过,编译时可能会更好。You could use a base class, optionally abstract, and use
dynamic_cast
. The argument will be checked at run-time. A compile-time would probably be better, though.我会考虑为消息建立一个基类,然后从该基类派生所有消息。然后,您将围绕事件传递指向基类的指针。
您可能会在基类中拥有一些基本功能,其中可能包括一个成员,说明它是什么类型的消息。这将允许您在转换到您需要的版本之前检查消息类型。
将基类作为最基本的消息类型很诱人,但我建议将其设为虚拟类,以便每个消息都必须进行强制转换才能使用。当复杂性(不可避免地)增加时,这种对称性使其更不容易出现错误
I would think about having a base class for the messages, and then derive all the messages from that base class. You will then be passing pointers to the base class around the events.
You will, presumably, have some basic funcitonaliy in the base class, which may include a member saying what type of message it is. This will allow you to check the message type before casting to the version you need.
It is tempting to have the base class as the most basic type of message, but I would advise making it a virtual class so that every message has to be cast to be used. This symmetry makes it much less prone to bugs later when the complexity (inevitably) increases