用function来存储lambda函数和函数对象从而实现回调
前言
昨天在使用
函数指针
类型的回调函数的时候,想尽各种方法都没有想到怎么
将回调函数外的参数传进回调函数中
,经前辈的指导,知道了
lambda表达式
和
函数对象
这两个工具,轻松解决我的问题,现在我就将相关的知识记录于此。
一。std::function
这里主要借助一个解码的伪项目,讲如何用function来声明回调函数,function的基本介绍参考此博文:
std::function
。
最重要的是,知道相比定义函数指针
typedef void (*StreamCallback)(void* stream, int size, int ch, int streamId);
,std::function 的优势是:
它的实例能存储、复制及调用任何可调用 (Callable)目标——
函数
、
lambda 表达式
、
bind 表达式
或
其他函数对象
,还有
指向成员函数指针
和
指向数据成员指针
。
1.声明回调函数
文件1:wrapper.h
#include <functional>
//注意:
//需要使用命名空间std才能使用function
//代替:typedef void (*StreamCallback)(void* stream, int size, int ch, int streamId);
using DecFrameCallback = std::function<void(void* frm, size_t size)>;
2. 声明需要调用回调函数的函数
文件1:wrapper.h
void OnFrame(DecFrameCallback cb)
{
decFrameCb_ = cb;
}
3. 调用函数OnFrame
文件2:capture.cpp
OnFrame([&](void* bufferToWrite, int imageSize) -> bool {
memcpy(bufferToWrite, info->data, info->size);
return true;
});
其中
[&](void* bufferToWrite, int imageSize) -> bool {
memcpy(bufferToWrite, info->data, info->size);
return true;
}
这部分就是lambda表达式,之后再详述。
其中
[&]
是指将capture.cpp中的参数全都传到回调函数中。
4. 总结
以上就是function应用于回调函数的主要过程,最后实现的结果就是,在wrapper.h文件中,可以通过decFrameCb_ 这个函数来调用capture.cpp中的函数:
void* bufferToWrite, int imageSize) -> bool {
memcpy(bufferToWrite, info->data, info->size);
return true;
}
二。lambda表达式
同样,这里也不介绍lambda表达式的基本知识,想了解的朋友可以参考这篇博文:
C++中的lambda表达式,这样学习就对了!
lambda表达式相比于普通的函数指针,有许多优势,此处主要是使用了lambda表达式的
“捕获动态变量”
的优势,这个优势使得在进行回调的时候,可以使用回调函数外面的变量,灵活性大幅提高。
下面是lambda在回调中的使用。
1. 调用函数OnFrame时作为回调函数
文件2:capture.cpp
OnFrame([&](void* bufferToWrite, int imageSize) -> bool {
memcpy(bufferToWrite, info->data, info->size);
return true;
});
这里,
(1)下面部分就是lambda表达式:
[&](void* bufferToWrite, int imageSize) -> bool {
memcpy(bufferToWrite, info->data, info->size);
return true;
}
(2) [&]代表:将capture.cpp所有变量传入lambda表达式。
(3)(void* bufferToWrite, int imageSize) 里面是lanmbda表达式的传参。
(4)bool 为lambda表达式的返回值。
(5)下面是lambda表达式的函数体:
{
memcpy(bufferToWrite, info->data, info->size);
return true;
}
三。函数对象
函数对象就是类的一种特殊用法,只是这个类里面只有一个函数A,而且在经过隐式转换后,可以直接使用这个类的对象来使用函数A,因此这种用法叫做函数对象。详细的内容参考此博文:
函数对象
。
函数对象也可以用来代替函数指针,同样也具有
“捕获动态变量”
的优势,但和lambda表达式相比,函数对象的
可移植性
会更好;而当使用次数变多之后,使用函数对象也会比使用lambda表达式
更加简洁
。
1. 函数对象在回调时的用法:
class DecFrameCallback {
public:
void operator()(void* frm, size_t size) {
//此处填所需的函数体
}
int x;
};
//在回调中使用函数对象
DecFrameCallback decframecallback;
decframecallback.x = 0;//将想要传入函数对象的变量赋值给函数对象里面public的变量
OnFrame(decframecallback);
其中,要特别注意的是:
- 和普通类的主要区别是函数对象的唯一函数是经过
operator()
隐式转换的,也就是在使用这个类的对象时,会自动隐式转换为类里面的唯一函数,因此这个类的对象才被称为函数对象。- 在实例化类时,不可以用动态分配内存的方式
decframecallback = new DecFrameCallback
,这样编译会报错,而像上文那样实例化
DecFrameCallback decframecallback;
则不会出现编译错误。具体原因我没找到,只是发现了这个现象。