在以下代码中,有泛型类
reg
和 regmap
以及从 mymap
类派生的用户定义类 regmap
。
在
mymap
类中,用户创建 reg
的实例并注册一个回调函数,该函数在调用 reg.read()
时执行。
这是完整的代码:
#include <iostream>
#include <string>
#include <functional>
using namespace std;
class regmap;
class reg {
public:
typedef void(regmap::*funcPtr)(reg);
std::function<void(reg)> callback_func;
void register_callback_func(regmap* map,funcPtr func){
callback_func = std::bind(func,map,std::placeholders::_1);
}
reg(string name):name(name){
callback_func = nullptr;
}
void read(){
if(callback_func){
callback_func(*this);
}
}
string name;
};
class regmap {
public:
regmap(){
}
virtual ~regmap(){
}
};
class mymap : public regmap {
public:
mymap():regmap(){
reg r1("r1");
//r1.register_callback_func(this,static_cast<void (regmap::*)(reg)>(&mymap::user_function));
r1.register_callback_func(this,&mymap::user_function);
r1.read();
}
void user_function(reg r){
cout<<" ....user_finction...."<<endl;
}
};
int main()
{
mymap map;
return 0;
}
上面的代码给出了编译错误:
test3_1.cpp: In constructor ‘mymap::mymap()’:
test3_1.cpp:41:61: error: no matching function for call to ‘reg::register_callback_func(mymap*, void (mymap::*)(reg))’
r1.register_callback_func(this,&mymap::user_function);
^
test3_1.cpp:13:10: note: candidate: void reg::register_callback_func(regmap*, reg::funcPtr)
void register_callback_func(regmap* map,funcPtr func){
^~~~~~~~~~~~~~~~~~~~~~
test3_1.cpp:13:10: note: no known conversion for argument 2 from ‘void (mymap::*)(reg)’ to ‘reg::funcPtr {aka void (regmap::*)(reg)}’
看起来派生类成员函数没有隐式转换为基类成员函数。
我有一些解决方法:
r1.register_callback_func(this,static_cast<void (regmap::*)(reg)>(&mymap::user_function));
我在
std::bind()
args中使用
register_callback_func
但是我想让用户的回调注册尽可能简单,这就是我选择
r1.register_callback_func(this,&mymap::user_function);
的原因
有什么解决办法吗?
该语言的规则是可以将基类型的指向成员的指针隐式转换为派生类型的指向成员的指针,但反之则不然。这很好,因为如果指向的成员在基类中,那么它也在派生类中,但反之则不然。从
Derived::*
到 Base::*
的转换可能会导致指向不存在的指针。该语言需要程序员明确确认这是有意的。
在您的情况下,您希望保护您班级的用户不必给出此明确的确认(又名
static_cast
)。我想说这很好,因为转换在语义上是 reg
的实现细节。一种解决方案是对您的职能所接受的内容更加自由。不要强迫 regmap
参与,而是编写一个接受任何兼容对象指针和指向成员的指针的模板。
// A type alias can be a template.
template <class T>
using funcPtr = void(T::*)(reg);
// A template function can accept any class.
template <class T>
void register_callback_func(T* map, funcPtr<T> func){
callback_func = std::bind(func, map, std::placeholders::_1);
// Alternatively, either of the following would also work:
//callback_func = [=](reg param) { return (map->*func)(param); };
//callback_func = [=](reg param) { std::invoke(func, map, param); };
}
如果出于某种原因您确实想将注册限制为从
regmap
派生的类,您可以向模板添加 requires std::is_base_of_v<regmap, T>
子句(然后您需要 #include <type_traits>
)。然而,这让我觉得这是不必要的限制。
如果
map
确实必须从regmap
派生,那么获得此结果的更直接方法是给regmap
一个要作为回调调用的虚函数,而不是深入研究指向成员的指针语法。
这种方法的缺点是每个对象仅限于一个回调。如果存在向同一个对象注册不同的成员函数指针的情况,那么这种方法是不够的。然而,有些人可能会忽略这个选项,所以我认为值得一提,至少对于未来的访客来说。
class reg;
class regmap {
public:
virtual ~regmap() = default;
virtual void callback(reg) = 0;
};
通过此设置,派生类可以选择直接将回调实现为
callback()
或简单地从其 callback()
的实现中调用另一个成员函数。您只需要调整 reg
类来注册一个对象,而不是一个对象加上一个指向成员函数的指针(并且更简单是一个优点)。
// If the member function `read()` is defined inline, then the definition
// of `regmap` is needed. Otherwise, a forward declaration is sufficient.
class regmap;
class reg {
regmap* callback_obj = nullptr;
public:
void register_callback_object(regmap* map){
callback_obj = map;
}
void read(){
if(callback_obj){
callback_obj->callback(*this);
}
}
};