C++父类方法接收子类成员方法作为参数报编译错误
问题背景
需要实现一个支持注册子类成员方法的父类接口,同时为成员方法类型定义别名简化冗长的类型书写,现有代码框架基本固定,编写过程中出现编译报错。
原实现代码如下:
#include <map> #include <string> using std::map; using std::string; struct ParentClass; struct DataClass; using my_handler_type = uint16_t (ParentClass::*)(DataClass&, DataClass&); struct ParentClass { void add_handler(string name, my_handler_type handler) { handlers.emplace(name, handler); } private: map<string, my_handler_type> handlers; }; struct ChildClass : ParentClass { private: uint16_t some_handling_function(DataClass &, DataClass & ); void setup_handler() { add_handler( "handler_one", &ChildClass::some_handling_function ); } };
编译报错
example.cpp: In member function ‘void ChildClass::setup_handler()’: example.cpp:31:37: error: cannot convert ‘uint16_t (ChildClass::*)(DataClass&, DataClass&)’ {aka ‘short unsigned int (ChildClass::*)(DataClass&, DataClass&)’} to ‘my_handler_type’ {aka ‘short unsigned int (ParentClass::*)(DataClass&, DataClass&)’} 31 | add_handler( "handler_one", &ChildClass::some_handling_function ); | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | | | uint16_t (ChildClass::*)(DataClass&, DataClass&) {aka short unsigned int (ChildClass::*)(DataClass&, DataClass&) example.cpp:14:51: note: initializing argument 2 of ‘void ParentClass::add_handler(std::string, my_handler_type)’ 14 | void add_handler(string name, my_handler_type handler) { | ~~~~~~~~~~~~~~~~^~~~~~~
错误原因
C++ 标准没有定义派生类成员函数指针到基类成员函数指针的隐式转换规则:哪怕两个函数的返回值、参数列表完全一致,uint16_t (ChildClass::*)(DataClass&, DataClass&) 和 uint16_t (ParentClass::*)(DataClass&, DataClass&) 也是两个独立的不同类型,直接传参必然触发类型不匹配的编译错误。
解决方案
最小改动方案(完全兼容现有固定框架)
定义的 my_handler_type 本身没有问题,只需要在注册子类成员方法时加上显式类型转换即可。该转换是C++标准明确允许的合法操作,只要后续调用handler时,调用方的实际对象类型和注册方法所属类匹配,就不会出现问题。
修改ChildClass::setup_handler中的注册逻辑:
void setup_handler() { add_handler( "handler_one", static_cast<my_handler_type>(&ChildClass::some_handling_function) ); }
注意:如果在纯
ParentClass实例上调用原本属于ChildClass的handler,会触发未定义行为,实际业务中只要保证handler和调用对象类型对应即可。
框架小幅优化方案(无需子类手动写类型转换)
如果允许对父类代码做极小改动,可以新增一个模板重载的add_handler接口,自动完成合法的类型转换,同时增加编译期校验避免传入非法类型的成员函数,子类原有注册代码不需要任何修改就能直接编译通过:
#include <type_traits> struct ParentClass { // 模板版本,接收子类成员函数指针 template<typename Derived> void add_handler(string name, uint16_t (Derived::*handler)(DataClass&, DataClass&)) { // 编译期校验传入的类必须是ParentClass的子类,避免误传无关类的方法 static_assert(std::is_base_of_v<ParentClass, Derived>, "Registered handler must belong to a subclass of ParentClass"); handlers.emplace(name, static_cast<my_handler_type>(handler)); } // 原有接收基类成员指针的接口可保留,兼容原有调用逻辑 void add_handler(string name, my_handler_type handler) { handlers.emplace(name, handler); } private: map<string, my_handler_type> handlers; };
内容的提问来源于stack exchange,提问作者Robert Hickman
相关产品推荐
相关产品推荐

