如何在 C++ 中定义接受派生类成员函数指针作为参数的类方法?
我正在使用在这里找到的 Signal-Slot C++ 实现: https://github.com/pbhogan/Signals 在我正在开发的用户界面库中实现事件侦听器。
面向用户的 API 如下所示:
class DialEventListener : public EventListener {
void handleEvent(Event event){
…
cout << "Event on" << event.source.name << end;
}
}
int main(){
Interactor dial1;
dial1.addListener(ROTATE_EVENT, DialEventListener());
…
}
要定义事件侦听器,用户需要对 EventListener
进行子类化以创建自定义事件处理程序。但在使用 Signals.h 代码的 Interactor::addListener() 实现中,您必须提供成员函数指针以及类实例指针以将 Delegate 连接到某个 Signal。
下面的代码片段显示了我如何尝试使用信号来实现事件侦听器。我使用从 EventType
到 Signal
的映射来解释触发多种类型事件的交互器。
typedef map< EventType, Signal1<Event> > EventTypeListenerMap;
class Interactor {
private:
EventTypeListenerMap listener_map;
public:
void TriggerEvent(Event e){
Signal1<Event> signal = listener_map[e.type];
signal(e);
}
void addListener(EventType e_type, EventListener listener){
…
//Find the signal for a particular kind of event.
Signal1<TUIKitEvent> signal = listener_map[e_type];
//Plug a delegate into that signal's slot
signal.Connect( &listener, &EventListener::handleEvent);
}
void addListener(EventType e_type, EventListener listener, void (EventListener::*handler)(Event)){
…
Signal1<TUIKitEvent> signal = listener_map[e_type];
signal.Connect( &listener, handler);
}
}
class EventListener {
virtual void handleEvent(Event event){
}
}
我使用 Signal-Slot 实现的事实应该对用户隐藏,因此我只编写了此版本的 Interactor::addListener(EventType, EventListener, void (EventListener::*))作为健全性检查,以确保我传递了正确的成员函数指针以绑定到该信号。但成员函数指针是特定于类的,因此如何定义一个带有签名的函数,该函数接受指向从 EventListener 派生的类的方法的指针,而该类可能尚不存在?
I'm using a Signal-Slot C++ implementation found here: https://github.com/pbhogan/Signals to implement event listeners within a user interface library I'm developing.
The user-facing API goes like so:
class DialEventListener : public EventListener {
void handleEvent(Event event){
…
cout << "Event on" << event.source.name << end;
}
}
int main(){
Interactor dial1;
dial1.addListener(ROTATE_EVENT, DialEventListener());
…
}
To define an event listener, a user would subclass EventListener
to create a custom event handler. But in the implementation of Interactor::addListener()
where I use the Signals.h code you have to provide a member function pointer as well as a class instance pointer to connect a Delegate to some Signal.
Below is an code snippet showing how I'm trying to use Signals to implement event listeners. I use a map from EventType
s to Signal
to account for an interactor that triggers multiple types of events.
typedef map< EventType, Signal1<Event> > EventTypeListenerMap;
class Interactor {
private:
EventTypeListenerMap listener_map;
public:
void TriggerEvent(Event e){
Signal1<Event> signal = listener_map[e.type];
signal(e);
}
void addListener(EventType e_type, EventListener listener){
…
//Find the signal for a particular kind of event.
Signal1<TUIKitEvent> signal = listener_map[e_type];
//Plug a delegate into that signal's slot
signal.Connect( &listener, &EventListener::handleEvent);
}
void addListener(EventType e_type, EventListener listener, void (EventListener::*handler)(Event)){
…
Signal1<TUIKitEvent> signal = listener_map[e_type];
signal.Connect( &listener, handler);
}
}
class EventListener {
virtual void handleEvent(Event event){
}
}
The fact that I'm using a Signal-Slot implementation should be hidden from the user, so I just wrote this version of Interactor::addListener(EventType, EventListener, void (EventListener::*))
as a sanity check to make sure I was passing the right member function pointer to be bound to that signal. But member function pointers are class specific, so how can one define a function with a signatures that accepts pointers to methods of classes derived from EventListener, which likely don't yet exist?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
回调有两种方法,要么提供一个具有将被调用的虚函数的接口,要么执行类型擦除以使其完全通用。在第二个版本中,您可以使用预装的类型擦除解决方案,或者您需要使用一些模板来自己实现。
您在第一个块中显示的预期用户代码遵循第一种方法,并且足以满足基本需求。但请注意,您可能希望在按值传递的几个位置中使用指针或引用:
请注意一些更改:
listener
参数是按引用而不是值传递的。如果按值传递,您将得到切片(仅复制EventListener
子对象,并且它不会是内的
。通过传递对基本类型的引用,您可以将其用作基类,同时对象保持其身份,DialEventListener
对象>addListener这反过来又要求您传入的对象不是临时的(您不能使用它)。
DialEventListener()
作为addListener
的第二个参数,因为临时的生命周期将在完整表达式的末尾结束,这意味着您将有一个悬空引用(在您的当前的实现中,信号实现中有一个悬空指针,一旦信号被触发,这很可能会导致 UB。此外,在
addListener
中,您不想复制。存储在地图中的信号,而是修改它,因此,在访问映射中的信号时,您应该使用引用而不是值。您的类的文档应该非常明确,因为传入的对象的生命周期必须比 Interactor 的生命周期长。 (或者允许取消注册,以便客户端可以在回调被销毁之前从
Interactor
中删除处理程序(同样,如果您不这样做,您将以 UB 结尾)另一种方法正在执行类型擦除,我从未使用过那个特定的信号库,所以我在这里随机应变。您正在使用的信号库(可能)在内部实现类型擦除,但是您需要能够转发确切的类型,直到该库可以执行擦除,或者在接口中使用不同的类型擦除库。
在接口中使用不同的类型擦除库可能会也可能不会,具体取决于信号实现中
connect
方法的语义。如果它复制第一个参数,那么您可能可以使用 std::function :假设库将复制第一个参数并在内部维护它自己的副本(即假设它的行为类似于 boost::signal 库)。在用户方面,他们必须执行类型擦除:
bind
的确切语法可能不是这样,我从未在 boost 库的新标准中使用过它,它会是>boost::bind( &AnotherListener::method, &listener, _1 );
...There are two approaches for callbacks, either you provide an interface that has a virtual function that will be called, or else you perform type-erasure to make it fully generic. In the second version you either use a prepacked solution for the type-erasure or you will need to use some templates to implement it yourself.
The expected user code that you show in the first block follows the first approach, and is sufficient for the basic needs. But note that you will probably want to use pointers or references in a couple of the places where you are passing by value:
Note a couple of changes: the
listener
argument is passed by reference rather than value. If you pass by value you will get slicing (only theEventListener
subobject will be copied, and it will not be aDialEventListener
object insideaddListener
. By passing a reference to the base type, you can use it as a base while the object maintains its identity.That in turn requires that the object you pass in is not a temporary (you cannot use
DialEventListener()
as second argument toaddListener
, as the lifetime of the temporary will end at the end of the full expression and that means you will have a dangling reference (in your current implementation you have a dangling pointer inside the signal implementation, which will most probably cause UB once the signal is triggered).Additionally, inside
addListener
you don't want to copy the signal stored in the map, but rather modify it, so again, you should use a reference rather than a value when accessing the signal in the map.The documentation of your class should be quite explicit in that the lifetime of the object passed in must outlive the
Interactor
(or else allow for deregistration so that the client can remove the handler from theInteractor
before the callback is destroyed (again, if you don't you will end in UB)The other approach is performing type-erasure, I have never used that particular signals library so I am playing by ear here. The signal library that you are using (probably) implements type-erasure internally, but you need to be able to forward the exact type until the lib can perform the erasure, or else use a different type erasure library in the interface.
Using a different type erasure library in the interface might or not be possible depending on the semantics of the
connect
method in your signals implementation. If it copies the first argument, then you can probably make do withstd::function
:Assuming that the library will copy the first argument and maintain it's own copy internally (i.e. assuming that it behaves somehow like
boost::signal
library). On the user side, they will have to perform the type erasure:The exact syntax of
bind
might not be that, I have never used it from the new standard in the boost library it would have beenboost::bind( &AnotherListener::method, &listener, _1 );
...