Object-C++关于回调的那些事

在我们学习编写程序当中 回调 是我们最常见的词语之一,回调 事件大大的增强了我们程序的逻辑性和可读性,是编程中不可缺少的魁宝。当然,我们怎么说也是学过 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的我们看得出,这泥马存在这循环引用呀。
我们先看看为什么会发生循环引用

《Object-C++关于回调的那些事》 发生循环引用

没错,就是这么坑爹,未使用过 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;

上面代码,相当于如下效果

《Object-C++关于回调的那些事》 弱引用

但是为什么上面没有使用 shared_ptr 的时候没有发生循环引用呢?大哥,我们已经手动 delete 了 cb_。

至此,博主抛砖引用的实现了两种 C/C++ 中的回调方法,接下来小伙伴们发挥自己的想象力,实现更的方式吧。

大哥,既然来了,就点个喜欢吧。

心如止水,奋力前行

    原文作者:Abson在简书
    原文地址: https://www.jianshu.com/p/b189a31ccee7
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞