在webrtc中lambda表达式的运用场景实在太过于广泛了,因此如何接是我们要弄明白.
webrtc 中如何"接" lambda表达式
如何理解lambda表达式?
lambda表达式的形式:
【捕捉列表】(参数列表)->返回值 { 函数体};
那么我们怎么来理解这个lambda表达式?
- lambda表达式是一个类,底层是通过仿函数来实现的.
- 捕捉列表可以理解为 决定类里面的成员变量.
我们来看个demo.
#include <iostream>
#include <string>
using namespace std;
class A
{
public:
A()
{
cout << "A:()" << std::endl;
}
A(const A&)
{
cout << "A::(A&) 拷贝构造函数" << endl;
}
A(A&&)
{
cout << "A::(A&&) 移动构造函数" << endl;
}
A operator=(const A&)
{
cout << "A operator=(const A&)" << endl;
}
A operator=(A&&)
{
cout << "A operator=(A&&)" << endl;
}
~A()
{
cout << "A::~A()析构函数" << endl;
}
};
int main(int argc,char **argv)
{
A a;
auto func = [a] {
};
func();
return 0;
}
我们按值的方式来捕捉A,是否会触发A的拷贝构造函数呢?
答案是会的,func()能被调用,本质因为,这个仿函数类里面存在有类似:
void operaotr() 类似的重载函数.
在我们编译的时候,编译器就会将lambda表达式转化为对应的仿函数.
(lambda表达式底层也有可能是基于模板来实现,大家可以参考 webrtc里面的FunctionView,挺具有参考意义的).
如何来接lambda表达式
接就是让lambda表达式成员类的成员变量,我们在上面说过 lamabda比倒是是一个类,那么接lambda表达式就容易理解了,直接把它视为一个普通类来接不就可以了。
但是接还是存在一些问题的.
比如:
int a = 100 ;
auto func = [] {};
int a; int 是 a的类型 ,但是 lambda表达式 func 只能用auto来表示,类型没有办法具体化,因为lambda表示的编译规则没有指定类名.
其实编译器是知道lambda的具体类型的,比如:
那么我们可以使用模板参数来接.
我们来以官方的一个具有代表性的类来举例说明:
ClosureTask:
template <typename Closure>
class ClosureTask : public QueuedTask {
public:
explicit ClosureTask(Closure&& closure)
: closure_(std::forward<Closure>(closure)) {}
private:
bool Run() override {
closure_();
return true;
}
typename std::decay<Closure>::type closure_;
};
lambda表达式,官方叫法叫 “闭包”,英文也就是 Closure.
这里ClosureTask为一个类模板, 它的成员就是一个 lambda表达式.
这里大家有没有想过为何要使用
typename std::decay::type closure_;
原因是对于lambda表达式,我们要同时支持 引用和移动构造的支持.
怎么来理解这句话呢?
我们在接的时候,要同时要支持以 拷贝构造和移动构造两种方式来接 lambda表达式的成员变量. (这句话大家理解下!)
我们来把它实例具体化:
移动构造:
功能:把lambda表达式,通过移动构造的方式接入进来.
auto func =[]{};
ClosrureTask task( std::move(func) );
这里面就必然涉及到类型的推导:(假设lambda表达式类型为Lambda类型,方便理解!)
explicit ClosureTask(Closure&& closure)
Closure类型: Lambda
closure 类型为: Lambda&&
typename std::decay::type closure_;等价于 Lambda closure_;
引用构造:
auto func =[]{};
ClosrureTask task( func );
explicit ClosureTask(Closure&& closure)
Closure类型: Lambda&
closure类型为: Lambda&& closure
正是因为Cosure类型为 Lambda& 类型,所以我们必须使用 std::decay 类做类型退化,目的是为了触发 lambda表达式成员变量的拷贝构造函数,否则就是直接引用 Lambda表达式的全套就会落入lambda表达式的陷阱。
typename std::decay::type closure_; 就等价于
typename std::decay< Lambda&>::type closure_;
展开后就是:Lambda closure_;
涉及知识点:
上面的分析,涉及到的知识点,主要是三个:
- 万能引用,也就 universal reference。
- 引用折叠
- 一些类的对象模型的概念
这三个要讲清楚,还需要花点功底和时间,同时需要结合大量测试例子进行结果追踪分析,这里篇幅肯定不够,我会再单独的开一篇文章来进行解释.
lambda表达式陷阱
lambda表达式最大的陷阱就是 捕捉的成员变量的生命周期, 比如:
{
int a;
int b;
auto func = [&a,&b] {};
// 把这个func添加到 task中去.
}
在{}后,a和b的生命周期就已经结束了,但是也许我们会把我们的lambda表达式给传递到我们的任务列表中,调用时候,忽然发现 a 和 b无法访问,导致程序出错,这一块 webrtc管理的还是相当严谨的,类似的使用场景一定要千万注意.