c++ 中类成员函数做回调函数的几种方式
引言
在C++ 编程中如果使用类成员函数作为回调函数,会遇到报错
error C2597: illegal reference to non-static member
这个错误原因为C++ 中非静态成员函数只有在创建对象时才会分配内存,对编译器来说,其无法确定回调函数的地址,所以编译器会报错。
方法
静态成员函数
按照系统报错提示将成员函数定义为静态。
缺点:
静态函数无法调用非静态成员函数,不利于函数的封装。
普通函数/友元函数
编写一个类外函数或者友元函数
缺点:
与上述方法的缺点一致
lambda 函数
用STL中的sort()做例子记录一下lambda函数用法。
int main()
{
std::array <int, 8> nums = { 1,7,3,8,4,6,2,5 };
std::sort(nums.begin(), nums.end(),
[](const int& a, const int& b) {
return (a > b);
});
for (auto& n : nums){
std::cout << n << std::endl;
}
return 0;
}
即用lambda函数做一个简单的回调函数。
下面介绍用lambda函数做一个封装函数,能将this传入sort中(此处会在后面分析)1
class myclass {
public:
std::array <int, 8> nums = { 1,7,3,8,4,6,2,5 };
bool max(const int& a, const int& b) {
return a > b;
}
int foo() {
auto wrapper = [&](const int& a, const int& b) { // lambda函数在捕获列表里通过引用(&)方式
// (当然直接捕获this或者采用取值(=)也是可以的)捕获了this
// 即通过wrapper函数传递this到sort函数中。
return max(a, b);
};
std::sort(nums.begin(), nums.end(), wrapper);
}
};
int main()
{
myclass myclass1;
myclass1.foo();
for (auto& n : myclass1.nums) {
std::cout << n << std::endl;
}
}
这样也是可以运行的。
同时可以推广到其他的回调函数中,例如 stm32 CMSIS中创建线程。
class myclass {
public :
void threadEntry()
{
for (;;)
{
...
}
}
void start_server()
{
auto wrapper = [](void* ctx) { // 此处不用捕获this的原因是
// 将this通过osThreadNew传递参数的方式传入了wrapper里,
// 使得编译器可以确定需要调用谁的函数。
((myclass *)ctx->threadEntry);
};
...
osThreadNew(wrapper, this, ...); // 本质上是一个回调函数绑定的过程
}
};
分析
- 为什么需要通过一个函数封装的形式才能使类的非静态成员函数做为回调函数使用呢?
因为普通的成员函数里都会隐含一个
this
,在类的非静态成员函数中访问类中其他非静态成员函数时,编译器会自动传递一个this
进去,从而使得在类内可以直接调用其他非静态成员函数。同时也是由于有一个this
存在,使得一个callback函数作为回调函数时就会因为隐含的this
指针使得函数参数个数不匹配,从而导致回调函数匹配失败,所以为例实现回调,类内的函数必须舍弃隐藏的this
,故可以通过将成员函数转化为静态成员函数或者封装wrapper等方式来实现调用。
- 那么为什么静态成员函数就可以呢?
因为静态成员函数属于类本身,在类加载时就已经分配内存了,可以通过
className::staticFuncName
直接访问,不要实例化对象就可使用。也正因为static不属于对象,而是属于类,所以静态成员函数没有this
这个参数,就不会造成参数个数不匹配的情况。
其他的方式
-
专门写一个类的静态函数做wrapper将动态函数封装在内,例如:
class myclass { public : void threadEntry() { for (;;) { // osDelay(); } } static void wrapper(void* ctx) { ((myclass*)ctx)->threadEntry(); } void start_server() { ... osThreadNew(wrapper, nullptr, ...); // 本质上是一个回调函数绑定的过程 } };
-
重载()
-
C++11新特性td::tr1::function和std::tr1::bind实现回调函数
-
虚函数特性做回调函数
-
将回调函数的指针,定义结构体对象的指针封装在一个结构体做回调函数
总结
程序实现方式是多种多样的,重要的是本质的理解。
见分析小节。 ↩︎