如何为特定签名的任意MyObject成员函数声明std::function?
问题
我正在实现一个简化版的Qt信号槽connect接口,期望的API如下:
class MyEvent; class MyObject; static void connect( MyObject* sender, std::string eventName, MyObject* listener, SomeCallable listenerMethod );
其中SomeCallable需要满足以下约束:
- 必须是
MyObject或其子类的实例方法; - 方法名称无限制;
- 仅接受一个
const MyEvent&类型参数,且无返回值。
请问该如何定义对应的std::function类型?仅用std::function能否实现上述严格约束?
我考虑过用std::function<void(const MyEvent&)>作为SomeCallable,让调用者自行绑定:
struct HisListener: public MyObject { void foo( const MyEvent& ); }; connect( some_sender, "some_event", some_listener, std::bind(&HisListener::foo, some_listener, std::placeholders::_1) );
但这种方式无法在编译期保证listener和listenerMethod的关联,我需要更严格的编译期约束。
解决方案
1. 仅用std::function无法实现严格约束
std::function是类型擦除容器,它只关心可调用对象的签名是否匹配,无法在编译期验证该对象是否是listener指向的MyObject子类的成员函数。使用者可以传入任意符合签名的可调用对象(比如普通函数、绑定了其他对象的函数、lambda),完全绕开listener参数的关联要求,达不到你想要的严格限制。
2. 用成员函数指针实现编译期约束
最直接的方式是将SomeCallable定义为**MyObject的成员函数指针类型**,这样编译期就能强制约束传入的方法符合要求:
首先定义成员函数指针类型:
// 匹配要求的成员函数签名:void(MyObject::*)(const MyEvent&) using MyObjectMethod = void (MyObject::*)(const MyEvent&);
然后修改connect函数的参数:
static void connect(MyObject* sender, std::string eventName, MyObject* listener, MyObjectMethod listenerMethod);
调用时直接传入子类的成员函数指针即可(子类成员函数指针会自动向上转型为MyObjectMethod):
struct HisListener: public MyObject { void foo(const MyEvent&); }; // 直接传入成员函数指针,编译期自动检查合法性 connect(some_sender, "some_event", some_listener, &HisListener::foo);
这种方式能在编译期确保:
- 传入的是
MyObject或其子类的成员函数; - 函数签名严格匹配(仅接受
const MyEvent&参数,无返回值)。
3. 模板版本:更友好的调用体验+更强约束
如果想让调用更简洁,同时在编译期明确检查子类继承关系,可以用模板推导成员函数指针:
#include <type_traits> template <typename Derived> static void connect(MyObject* sender, std::string eventName, Derived* listener, void (Derived::*listenerMethod)(const MyEvent&)) { // 编译期强制检查Derived必须是MyObject的子类 static_assert(std::is_base_of_v<MyObject, Derived>, "Listener class must inherit from MyObject"); // 将成员函数指针与listener绑定为std::function,方便后续存储和调用 auto bound_func = std::bind(listenerMethod, listener, std::placeholders::_1); // 这里将bound_func存入sender的事件映射表,比如: // sender->event_map[eventName].push_back(bound_func); }
调用时无需显式指定模板参数,编译器会自动推导:
connect(some_sender, "some_event", some_listener, &HisListener::foo);
这种方式不仅保留了编译期约束,还能避免子类成员函数指针向上转型的潜在问题,同时调用体验更自然。
内容的提问来源于stack exchange,提问作者jiandingzhe

