如何在c++中定义一个接受派生类成员函数指针作为参数的类方法

How I do define a class method that accepts a derived class member function pointer as a parameter in C++?

本文关键字:函数 成员 派生 指针 类方法 参数 c++ 定义 一个      更新时间:2023-10-16

我在这里使用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以创建自定义事件处理程序。但是在Interactor::addListener()的实现中,我使用Signals.h代码,你必须提供一个成员函数指针以及一个类实例指针来连接一个委托到一些信号。

下面是一个代码片段,展示了我如何尝试使用Signals来实现事件侦听器。我使用从EventType s到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){
    }
}

我使用信号槽实现的事实应该对用户隐藏,所以我只是写了这个版本的Interactor::addListener(EventType, EventListener, void (EventListener::*))作为一个完整性检查,以确保我传递了正确的成员函数指针绑定到那个信号。但是成员函数指针是特定于类的,所以如何定义一个带有签名的函数,该签名接受指向从EventListener派生的类的方法的指针,这些方法可能还不存在?

有两种回调方法,要么提供一个具有将被调用的虚函数的接口,要么执行类型擦除以使其完全泛型。在第二个版本中,你可以使用预先打包的类型擦除解决方案,或者你需要使用一些模板来自己实现它。

您在第一个块中显示的预期用户代码遵循第一种方法,并且足以满足基本需求。但请注意,您可能希望在传递值的几个地方使用指针或引用:

// user code
Interactor dial;
DialEventListener dial_listener;
dial.addListener( ROTATE_EVENT, dial_listener );
// implementation
void Interactor::addListener( EventType event, EventListener & listener ) {
   Signal1<TUIKitEvent> & sgn = listener_map[event];
   sgn.Connect( &listener, &EventListener::handleEvent );
};

注意几个变化:listener参数是通过引用而不是值传递的。如果你按值传递,你会得到切片(只有EventListener子对象会被复制,它不会是addListener中的DialEventListener对象)。通过传递对基类型的引用,您可以在对象保持其标识的同时将其用作基。

这反过来要求您传入的对象不是临时的(您不能使用DialEventListener()作为addListener的第二个参数,因为临时的生命周期将在完整表达式的末尾结束,这意味着您将有一个悬空引用(在您当前的实现中,您在信号实现中有一个悬空指针,一旦信号被触发,这很可能会导致UB)。

另外,在addListener内部,你不希望复制存储在映射中的信号,而是修改它,所以,当访问映射中的信号时,你应该使用引用而不是值。

你的类的文档应该非常明确,传入的对象的生命周期必须超过Interactor(或者允许注销,以便客户端可以在回调被销毁之前从Interactor中删除处理程序(再次,如果你不这样做,你将以UB结束)

另一种方法是执行类型擦除,我从未使用过特定的信号库,所以我在这里随机应变。您正在使用的信号库(可能)在内部实现了类型擦除,但是您需要能够转发确切的类型,直到库可以执行擦除,否则在接口中使用不同的类型擦除库。

// Transferring the exact type to the signal lib:
class Interactor {
// ...
public:
   template <typename Listener>
   void addListener( EventType event, Listener & listener, void (Listener::*callback)(Event) ) {
      Signal1<TUIKitEvent> & sgn = listener_map[event];
      sgn.connect( &listener, callback );
   }
//...

在接口中使用不同的类型擦除库可能是可能的,也可能是不可能的,这取决于信号实现中connect方法的语义。如果它复制了第一个参数,那么您可能可以使用std::function:

class Interactor {
//...
public:
   void addListener( EventType event, std::function< void (Event) > callback ) {
       Signal1<TUIKitEvent> & sgn = listener_map[event];
       sgn.connect( callback, &std::function<void(Event)>::operator() );
   }
//...
};

假设库将复制第一个参数,并在内部维护它自己的副本(即假设它的行为类似于boost::signal库)。在用户端,他们必须执行类型擦除:

struct AnotherListener {   // no need to explicitly derive from EventListner now!
   void method( Event e ) {}
};
int main() {
   Interactor dial;
   AnotherEventListener listener;
   dial.addListener( dial, std::bind( &AnotherListener::method, &listener ) );
}

bind的确切语法可能不是这样,我从来没有在boost库的新标准中使用过它,它应该是boost::bind( &AnotherListener::method, &listener, _1 );