C++11 成员函数作为回调函数的使用方式
作者:歪锅锅
C++11成员函数作为回调函数使用
std::bind()被广泛地应用在新式的回调函数中。
C++11以前类的普通成员函数不能作为回调函数去注册,因为将普通成员函数注册给对方,但对方使用这个函数指针时,就会发生参数列表匹配的问题,因为少了隐含的this。
静态成员函数不包含this指针,所以一般将静态成员函数注册给对方。
C++11推出std::bind()和std::function搭配,前者生成新的调用对象,参数个数可以小于绑定函数的参数个数,少的参数,按位占用。后者保存函数调用类型的函数对象,使用该对象进行设置参数即可。
示例1
先看一个例子来热热身,熟悉一下std::bind和std::function
#include <functional> //所需std::bind和std::function头文件 #include <iostream> #include <map> using namespace std; // 使用std::bind时记得和::bind区别开,就怕作用于污染,误用::bind //除法运算 class Division { public: int operator()(int i, int j) { return i / j; } }; //乘法运算 int Multiplication(int i, int j) { return i * j; } //减法运算 int Substraction(int i, int j) { return i - j; } //回调注册函数 int CallbackReg(function<int(int, int)> &func, int i, int j) { return func(i, j); } //回调注册函数1, int CallbackReq1(function<int(int)> &&func, int i) { return func(i); } int main() { // 此function接受函数调用类型为int(int, int)的调用对象 function<int(int, int)> func1 = [](int i, int j) { return i + j; }; //lambda function<int(int, int)> func2 = &Substraction; //函数指针 function<int(int, int)> func3 = Multiplication; //函数名 function<int(int, int)> func4 = Division(); //重载调用运算符的对象 //可将function类型存在容器中,来一次映射 map<int, function<int(int, int)>> mpFuncs; mpFuncs[1] = func1; mpFuncs[2] = func2; mpFuncs[3] = func3; mpFuncs[4] = func4; //这里做着玩,映射一个数字和字符串 map<int, string> mpOprs{{1, " + "}, {2, " - "}, {3, " * "}, {4, " / "}}; // 便利map调用容器内函数对象们 for (auto& it : mpFuncs) { cout << "calculator :" << 20 << mpOprs[it.first] << 5 << " = " << CallbackReg(it.second, 20, 5) << endl; } //使用std::bind,产生一个新的调用对象bindFunc(int i), 200作为int Multiplication(int i, 200) //std::placeholders 有个N个占位符(vs此版为20个):_N,表示占用绑定函数的第n个位子 int pre = 300; auto bindFunc = std::bind(Multiplication, placeholders::_1, pre); cout << bindFunc(3) << endl; //bind最重要的一点在于参数绑定,如下例注册回调,参数就从2个变成了1个 cout << CallbackReq1(std::bind(Multiplication, placeholders::_1, 200), 2) << endl; return 0; }
calculator :20 + 5 = 25
calculator :20 - 5 = 15
calculator :20 * 5 = 100
calculator :20 / 5 = 4
900
400
好,在了解了std::bind和std::function之后来看一个平时常遇到的C++式的回调函数注册
示例2
#include <functional> #include <iostream> #include <string> #include <memory> using namespace std; using namespace std::placeholders; //占位符_N所在的命名空间 using CallBackFuncType = function<void(string const&)>; class Client { public: string name; CallBackFuncType serverFunc; Client() :name("Vergo"), serverFunc(nullptr) {} ~Client() {} void SetCallBack(const CallBackFuncType &func) { serverFunc = func; } void DoCallBack() { serverFunc(name); } }; class Server { public: Client *m_clt; Server() : m_clt(nullptr) { m_clt = new Client; } ~Server() { if (m_clt) delete m_clt; m_clt = nullptr; } //回调函数本数 void MyCallBackFunc(string const& str) { cout << "The name of client is " << str << endl; } //注册回调,将this指针绑定到回调函数中 void RegCallBackFunc() { if (!m_clt) return; m_clt->SetCallBack(CallBackFuncType(std::bind(&Server::MyCallBackFunc, this, _1))); } //回调 void GiveMeCallBack() { if (!m_clt) return; m_clt->DoCallBack(); } }; int main() { Server testClass; testClass.RegCallBackFunc(); testClass.GiveMeCallBack(); return 0; }
The name of client is Vergo
类成员函数作为回调函数的方法及注意点
编程中遇到一个错误,提示为error C2597: illegal reference to non-static member
即因为一个类的静态成员函数调用了类的非静态成员变量,而报错。
下面具体介绍一些相关知识点,以防下次再出错。
类成员函数当回调函数的方法
方法一:回调函数为普通的全局函数,但在函数体内执行类的成员函数
在创建线程调用回调函数时,传入类对象的指针(比如this指针)作为参数,并在回调函数中把void*强制转换为类的指针(MyClass*),就能使用该指针调用类的成员函数。
这样做的原理是把当前对象的指针当作参数先交给一个外部函数,再由外部函数调用类成员函数。以外部函数作为回调函数,但执行的是成员函数的功能,这样相当于在中间作了一层转换。
缺点:回调函数在类外,影响了封装性。
方法二:回调函数为类内静态成员函数,在其内部调用类的非静态成员函数
此时需要一个指向类本身的、类的静态成员变量指针(static MyClass* CurMy),用来存储当前回调函数调用的对象,相当于法1中给回调函数传入的指针参数。在回调函数中通过CurMy指针调用类的成员函数。
优点:
- 1、解决了法1的封装性问题
- 2、没有占用callback的参数,可以从外界传递参数进来
缺点:每个对象启动子线程前一定要注意先让CurMy正确的指向自身,否则将为其它对象开启线程。
方法三:对成员函数进行强制转换,使其作为回调函数
这个方法是原理是,MyClass::func最终会转化成 void func(MyClass *this);即在原第一个参数前插入指向对象本身的this指针。可以利用这个特性写一个非静态类成员方法来直接作为线程回调函数。
typedef void* (*FUNC)(void*); FUNC callback = (FUNC)&MyClass::func;
对编译器而言,void (MyClass::*FUNC1)()和void* (*FUNC)(void*)这两种函数指针虽然看上去很不一样,但他们的最终形式是相同的,因此就可以把成员函数指针强制转换成普通函数的指针来当作回调函数。在建立线程时要把当前对象的指针this当作参数传给回调函数(成员函数func),这样才能知道线程是针对哪个对象建立的。
注意:此方法中FUNC函数的参数一定要是void*,这样才能在编译后把this指针转变为MyClass *this。
优点:法3的封装性比法2更好,因为不涉及多个对象共用一个静态成员的问题,每个对象可以独立地启动自己的线程而不影响其它对象。
为什么回调函数必须为静态函数?
普通的C++成员函数都隐含了一个“this”指针参数,当在类的非静态成员函数中访问类的非静态成员时,C++编译器通过传递一个指向对象本身的指针给其成员函数,从而能够访问类的数据成员。也就是说,即使你没有写上this指针,编译器在编译的时候自动加上this的,它作为非静态成员函数的隐含形参,对各成员的访问均通过this进行。
正是由于this指针的作用,使得将一个CALLBACK型的成员函数作为回调函数时就会因为隐含的this指针使得函数参数个数不匹配,从而导致回调函数匹配失败。所以为了实现回调,类中的成员函数必须舍弃掉隐藏的this指针参数。因此,类中的回调函数必须为静态函数,加上static关键字。
类的静态成员函数如何访问非静态成员?
静态成员不属于某个具体的对象,而是被所有对象所共享。即静态成员属于整个类,不属于具体某个对象;非静态成员属于具体某个对象。因而静态成员函数只能访问类的静态成员,不能访问类中非静态成员。
那么,如何让静态函数访问类的非静态成员?
方法是:对于静态成员函数,我们显示的为其传递一个对象的首地址(该类的指针)。一般在这个静态成员函数的形参列表中加入一个 void* 类型的参数,来保存对象的首地址。并在该函数内部对该参数进行类型转换,通过类型转换后的参数来调用非静态成员。
或者用一个类的全局指针数组,保存每一个创建出来的类的this指针,用全局指针去调用。
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。