在我们学习编写程序当中 回调
是我们最常见的词语之一,回调
事件大大的增强了我们程序的逻辑性和可读性,是编程中不可缺少的魁宝。当然,我们怎么说也是学过 Object-C
的人,再怎么说我们也知道在 OC 当中回调事件有代理,block 这两种惯用的模式,但是,在 C/C++ 中,我们又如何使用回调呢?使用回调又如何确保安全(当然指线程安全和防止内存泄露呢)?今天博主就带大家好好认识一番。
博主先抛砖引玉,写出两种 C/C++ 常用的回调手段,函数指针
和 lambda 表达式
。
1、使用函数指针
首先我们先理解一下什么函数指针:函数指针,看名字也就知道嘛,就是指向函数的指针咯,貌似好像是废话,额,我们就实操讲解一下吧。
void function_a() {
printf("大家好,我就是一个函数");
};
// 调用
void (*f_p)() = NULL; // 一个函数指针
f_p = &function_a;
f_p();
运行上面代码,得到的 logger 为 :大家好,我就是一个函数
分析:
上面代码中,我们首先定义了一个函数 void function_a()
,之后我们就能看到所谓的函数指针定义了 void (*f_p)()
,很明显,它指向了函数 function_a
的地址,之后我们便能通过函数指针来直接使用函数了。
实战:
那么我们该如何使用函数指针实现我们的回调呢?我们模仿工程当中使用,来为大家解决疑惑
Class CBClass {
public:
std:string m_str;
void (*m_callback)();
}
void fun_callback(CBClass *cb){
cb->m_str = "we reset m_str";
}
// 调用
CBClass *cb_ = new CBClass();
cb->m_callback = &fun_callback;
cb->m_callback(cb);
std::cout << cb->m_str << std::endl;
运行上面程序,得到的 loggler 为: we reset m_str
这就是我们在用 C++ 面向对象的时候使用的回调,个人感觉,还是跟代理有点想得,因为你还可以设置一个 auto
的代理变量,然后直接通过函数指针调用其方法,具体怎么设计这里就不做过多的描述了,留给小伙伴们一个实践的命题吧。
2、使用 lambda 表达式
很显然, lambda 表达式
是 C++ 11 出来的产物,一部分年老的程序员还是对其抱着观望的态度,不敢贸然使用,但是,你觉我们像是年老的程序员嘛 0 0,不,我们是代码的搬运工。好吧,我们来理解什么是 lambda 表达式
auto lambda_ = [=]() {
cout << "大家好我是一个 lambda 表达式"
};
auto lambda_ = [&]() {
cout << "大家好我又是一个 lambda 表达式"
};
上面代码中使用了两种 lambda 表达式
,学过 Swift 的小伙伴们都知道啥是 值类型
和 引用类型
,我们同时可以理解为 [=]
相当于 值类型
, 而 [&]
相当于引用类型。当然,也有 Object-C
的理解方法,[=]
相对与在你使用 block
中单单只是使用外部变量的值,并不关心使用变量的改变,具有使用如下
int a = 10;
void (^Callback)() = ^() {
NSLog(@"%d", a);
};
但是,如果你想要不管是 lambda
内部或外部修改引用变量的值时,就要使用到 [&]
,相当于 Object-C
中的 __block
使用了,如下
__block int a = 10;
void (^Callback)() = ^() {
NSLog(@"%d", a);
};
a = 20;
好了,lambda
表达式这么像我们的 Object-C
的 block,相信大家都异常的欢喜了,但是我们在实战当中如何使用 lambda
呢?
Class CBClass {
using Callback = function<void()>;
public:
std:string m_str;
Callback m_callback;
}
// 调用
CBClass *cb_ = new CBClass();
cb_->m_callback = [=](){
cb_->m_str = "we reset m_str";
};
cb_->m_callback();
cout << cb_->m_str << endl;
delete cb_;
运行以上代码,得到的 logger 为 : we reset m_str
大家看出,lambda
的调用方式真的是跟 block
像得不能再像了。
但是是什么让老一辈的 C++ 对这种新语法往而却步呢? 难道 lambda
会坑得他们变成宝字辈?
没错,lambda
也有坑。已上面 CBClass
为列子观看以下代码。
shared_ptr<CBClass> bc_ = make_shared<CBClass>();
cb_->m_callback = [=](){
cb_->m_str = "we reset m_str";
};
cb_->m_callback();
cout << cb_->m_str << endl;
上面代码中,我们使用了共享指针 shared_ptr
来让 CBClass
使用自动引用计数器,将内存交给系统管理,这段运行并不会崩溃,也不会报错,唯一的致命点在于内存泄露,用惯了 Object-C ARC
的我们看得出,这泥马存在这循环引用呀。
我们先看看为什么会发生循环引用
没错,就是这么坑爹,未使用过 C++11 特性的旧程序员们,又怎么料到这种事情呢,然后内存过多地方如此的写,程序崩溃0 0。
为了解决上面的循环引用,我们当然要将对象实现弱引用,让不让 lambda
来为我们的 cb_
对象管理内存咯。
shared_ptr<CBClass> bc_ = make_shared<CBClass>();
auto weak_cb = cb_->get();
cb_->m_callback = [=](){
weak_cb->m_str = "we reset m_str";
};
cb_->m_callback();
cout << cb_->m_str << endl;
上面代码,相当于如下效果
但是为什么上面没有使用 shared_ptr
的时候没有发生循环引用呢?大哥,我们已经手动 delete 了 cb_。
至此,博主抛砖引用的实现了两种 C/C++ 中的回调方法,接下来小伙伴们发挥自己的想象力,实现更的方式吧。
大哥,既然来了,就点个喜欢吧。