就我目前了解所知,有三种函数可以作为回调函数:
1.普通函数
2.静态函数(我用得少没有写,直接跳过)
3.成员函数
1.普通函数作为注册函数普通函数作为回调函数,比较简单,只要函数签名(返回值类型+参数类型)一致就可以了。
因为普通函数不是类成员函数,如果想要访问类成员,在执行回调函数的时候,要把对象指针传给回调函数,如下代码:
namespace yy0
{
//普通全局函数
void call_back(void* pointer);
class A
{
public:
A() {
//初始化指针
p_call_back = NULL;
//单数最大的数
num = 9;
}
~A() {}
public:
//打印这个数来验证是否正常调用回调函数
int num;
private:
//指向回调函数的地址的指针
void(*p_call_back)(void*);
public:
//用于注册回调函数
void register_call_back(void(*p)(void*)) {
if (p)
p_call_back = p;
}
//执行回调函数
void run_call_back() {
if (p_call_back)
{
//把对象指针传递出去
p_call_back(this);
}
}
//测试函数
void test() {
//注册
register_call_back(call_back);
//执行
run_call_back();
}
};
void call_back(void* pointer)
{
if (pointer)
{
//需要进行指针转换
A* p = (A*)pointer;
cout << "打印的值:" << p->num;
}
}
}
int main()
{
yy0::A a;
a.test();
getchar();
return 0;
}
结果正确打印,说明回调函数正常调用:
也可以定义一个全局的类对象指针:
namespace yy0
{
//前置声明
class A;
//普通全局函数
void call_back();
//全局的类对象指针
A* pointer = NULL;
class A
{
public:
A() {
//给全局类对象指针赋值
pointer = this;
//初始化指针
p_call_back = NULL;
//单数最大的数
num = 9;
}
~A() {}
public:
//打印这个数来验证是否正常调用回调函数
int num;
private:
//指向回调函数的地址的指针
void(*p_call_back)();
public:
//用于注册回调函数
void register_call_back(void(*p)()) {
if (p)
p_call_back = p;
}
//执行回调函数
void run_call_back() {
if (p_call_back)
{
//把对象指针传递出去
p_call_back();
}
}
//测试函数
void test() {
//注册
register_call_back(call_back);
//执行
run_call_back();
}
};
void call_back()
{
if (pointer)
{
//需要进行指针转换
A* p = (A*)pointer;
cout << "打印的值:" << p->num;
}
}
}
这也可以正确执行,但是这种定义全局的对象指针有风险。
如果只创建一个A的对象,就可以正常使用,不会出现什么太大问题。
但是,一旦创建的对象个数≥2,那么就造成数据读取错误的问题。
可以想象一下,创建对象a1时,全局对象指针pointer是指向a1的位置,那么读取的pointer->num,是a1对象的num。
然后再创建a2,那么全局对象指针pointer就变成了指向a2的位置(因为pointer是个全局变量,从始至终只有一个这个变量),那么执行a2.text(),pointer->num读取的是a2的num。
如果执行a1.text(),那么此时,pointer->num读取的也是a2的num,而不是a1的num。
更严重的是,一旦删除了a1或者a2,就会造成另外一个对象访问内存失败的问题。
这个就自行上网查看吧,我用的少就不写了。
假设场景:A类成员函数作为B类回调函数
《深度探索C++对象模型》这本书讲到,类成员函数都有一个隐藏参数用于传递this指针,这个this传递给函数由编译器来完成,不需要用户来做。
直接上代码:
namespace yy3
{
class B
{
public:
B() {
pointer = NULL;
}
~B() {}
public:
//存放A类的this指针
void* pointer;
//指向回调函数
void(__stdcall *pCallBack)(void*);
public:
/*
@函数作用:注册回调
@输入参数:
void(*p)(void*) -- 输入A类的回调函数的地址
void* p_this -- 输入A类的this指针
*/
//②
void register_fun(void(__stdcall *p)(void*), void* p_this) {
pCallBack = p;
pointer = p_this;
}
//执行回调
//③
void run_call_back() {
if (pCallBack)
pCallBack(pointer);
}
};
class A
{
public:
A() {
a = 5;
}
A(int num) {
a = num;
};
~A() {}
public:
//在A类中定义一个B类的变量
B b;
//拿来测试的变量
double a;
//定义联合,不知道原理,网上查到的技巧
union for_callback {
void(__stdcall *fun_int_c)(void*);
void (A::*fun_in_class)(void*);
}fp;
public:
//要拿来注册的回调函数
void call_back(void* p) {
A* pointer = (A*)p;
//能打印出正确的a值就对了
cout << "a:" << pointer->a << endl;
}
//测试函数
//①
void test() {
fp.fun_in_class = &A::call_back;
b.register_fun(fp.fun_int_c, this);
b.run_call_back();
}
};
}
int main()
{
yy3::A a;
a.test();
getchar();
return 0;
}
首先来解释一地方:
1.__stdcall声明:这个看情况,我在公司电脑写的时候不需要加这个关键字,自己的电脑就要加这个。
就是一个传参约定,可以上网查。
2.
//定义联合,不知道原理,网上查到的技巧
union for_callback {
void(__stdcall *fun_int_c)(void*);
void (A::*fun_in_class)(void*);
}fp;
使用union,这个说是为了逃避编译器检查,原理我也不太懂,如果有知道原理的大神,麻烦告诉一下下,感谢感谢。
我直接就拿来用了。
3.前面说了成员函数有个隐含传递指针的参数,所以函数指针:
//指向回调函数
void(__stdcall *pCallBack)(void*);
需要定义参数为void*的函数指针,用于传递A类的this指针
4.因为函数指针是B类的成员,而函数指针接受的参数是A类的this指针,我们不能直接这样使用:
void run_call_back() {
if (pCallBack)
pCallBack(this);
}
这个pCallBack(this)中的this是指向B类对象的地址而非A类对象的地址,因此,在B类定义一个成员:void* pointr,用于保存A类对象的指针,然后这样使用
//执行回调
void run_call_back() {
if (pCallBack)
pCallBack(pointer);
}
这样就运行回调函数,同时传递A类对象指针。
5.(无参这一点单独在这里说)当然,虽然成员函数有自带隐藏参数,我们也可以把它转换成无参的函数,修改这些地方:
//【1】
//指向回调函数
void(__stdcall *pCallBack)(void*);
//修改为
void(__stdcall *pCallBack)();
//【2】
void register_fun(void(__stdcall *p)(void*), void* p_this) {
pCallBack = p;
pointer = p_this;
}
//修改为
void register_fun(void(__stdcall *p)(), void* p_this) {
pCallBack = p;
pointer = p_this;
}
//【3】
//执行回调
void run_call_back() {
if (pCallBack)
pCallBack(pointer);
}
//修改为
void run_call_back() {
if (pCallBack)
pCallBack();
}
//【4】
union for_callback {
void(__stdcall *fun_int_c)(void*);
void (A::*fun_in_class)(void*);
}fp;
//修改为
union for_callback {
void(__stdcall *fun_int_c)();
void (A::*fun_in_class)();
}fp;
//【5】
//要拿来注册的回调函数修改为
void call_back() {
cout << "a:" << this->a << endl;
}
这种情况编译能通过,但是void call_back()使用this指针,是无法正确读取内存的值,如下
言归正传。
成员函数转为带一个void*参数的函数运行情况如下:
结果也是一个不正确的值,因此进行调试查看,把断点放在这个函数上,发现了一个奇怪的问题:
//要拿来注册的回调函数
void call_back(void* p)
{
A* pointer = (A*)p;
//能打印出正确的a值就对了
cout << "a:" << pointer->a << endl;
}
pointer是A类对象的指针,pointer通过函数指针pCallBack(pointr)传递给了call_back(void* p),从理论上讲,p的值要与pointer保持一致才对。
但是p的值与pCaalBack相同,也就是p是函数指针,特别奇怪。
我也不知道什么原因,所以如果有人知道,麻烦跟我讲一下,在这里先谢谢了。
我无法解决这个问题,所以尝试了将函数指针转为带有两个void*参数的函数,竟然可以传递正确的this指针,算是瞎猫碰上死耗子吧,代码跟上面类似,如下:
namespace yy3
{
class B
{
public:
B() {
pointer = NULL;
}
~B() {}
public:
//存放A类的this指针
void* pointer;
//指向回调函数
void(__stdcall *pCallBack)(void*, void*);
public:
/*
@函数作用:注册回调
@输入参数:
void(*p)(void*,void*) -- 输入A类的回调函数的地址
void* p_this -- 输入A类的this指针
*/
void register_fun(void(__stdcall *p)(void*, void*), void* p_this) {
pCallBack = p;
pointer = p_this;
}
//执行回调
void run_call_back() {
if (pCallBack)
//需要两个指针作为参数,干脆就传递两个pointer吧
pCallBack(pointer,pointer);
}
};
class A
{
public:
A() {
a = 5;
}
A(int num) {
a = num;
};
~A() {}
public:
//在A类中定义一个B类的变量
B b;
//拿来测试的变量
double a;
//定义联合,不知道原理,网上查到的技巧
union for_callback {
void(__stdcall *fun_int_c)(void*, void*);
void (A::*fun_in_class)(void*, void*);
}fp;
public:
//要拿来注册的回调函数
void call_back(void* p, void* pp)
{
A* pointer = (A*)p;
//能打印出正确的a值就对了
cout << "a:" << pointer->a << endl;
}
//测试函数
void test() {
fp.fun_in_class = &A::call_back;
b.register_fun(fp.fun_int_c, this);
b.run_call_back();
}
};
}
结果是正确的:
从图上可知,pCallBack(函数指针)的值,与p和pp都不同,无论是p还是pp,这两个值都是A类对象的地址,也就是说,已经成功把A的this指针传递进来了。
因此结果也是正确的。
分享这次奇怪的经历,感谢阅读。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)