(1)在lambda表达式的捕获列表只能捕获非静态的局部变量,对于全局变量和静态变量,可以在lambda表达式函数体中直接进行使用。
// 全局变量
int l = 0;
int main()
{
// 静态变量
static int s = 0;
auto f = []() {
s = 100;
l = 50;
};
return 0;
};
并且在lambda中进行修改也是同样有效。
(2)按值捕获时,可以通过mutable开启内部的修改性,而修改后下次再调用该lambda表达式的时候,数值会是修改之后的。
int main()
{
int i = 0;
auto f = [i]() mutable
{
i++;
std::cout << "lambda: " << i << std::endl;
};
f();
f();
f();
std::cout << "Main Fun: " << i << std::endl;
return 0;
};
(3)不需要捕获const int变量
如果该变量是const非volatile的整型或枚举类型,并且已经常量表达式初始化,那么不需要在捕获列表中捕获;如果声明为constexpr那么就算不是int也能直接捕获。
/** 正确,const int 类型能在lambda中直接捕获 */
const int i = 10;
auto f = [] ()
{
return i;
};
/** 正确,const int 类型能在lambda中直接捕获 */
constexpr int i = 10;
auto f = [] ()
{
return i;
};
/** 错误,只能不捕获const int */
const float d = 10.;
auto f = [] ()
{
return d;
};
/** 正确,constexpr也能直接不捕获 */
constexpr float d = 10.;
auto f = []()
{
return d;
};
注意:此处的不捕获是针对于仅读取值,如果需要使用,那么一定要进行捕获。
constexpr float d = 10.;
auto f = [=]()
{
// 进行使用
return &d;
};
(4)捕获的变量大小
还需注意的是,如果前面捕获了一堆东西,但是在lambda中没有进行使用,相当于你什么都没有捕获。
/** 没有进行使用 */
int a;
int b;
int c;
int d;
auto f = [=]()
{
return 1;
};
std::cout << sizeof f << std::endl; // 输出 1
/** 对a,b进行使用,返回 a + b 的大小 */
int a;
int b;
int c;
int d;
auto f = [=]()
{
int w = a + b;
return w;
};
std::cout << sizeof f << std::endl; // 输出 8
我们将lambda表达式解开,看看它内部是怎么做的:
/** 原来的式子 */
int main()
{
int i = 0;
int j = 0;
static int s = 0;
auto f = [i, &j]()
{
std::cout << i + 1 << std::endl;
std::cout << j + 1 << std::endl;
std::cout << s + 1 << std::endl;
};
f();
return 0;
};
/** 解开lambda后 */
int main()
{
int i = 0;
// 声明一个class,专门供lambda表达式使用
class __lambda_8_11
{
public:
// 使用inline表达式,把lambda的函数体移到()的重载当中
inline /*constexpr */ void operator()() const
{
std::cout.operator<<(i + 1).operator<<(std::endl);
std::cout.operator<<(j + 1).operator<<(std::endl);
// 静态或全局的在此处可以直接使用
std::cout.operator<<(s + 1).operator<<(std::endl);
}
private:
// 根据捕获参数声明成私有成员变量
int i;
int & j;
public:
// 在构造函数中传入捕获列表的参数
__lambda_8_11(int & _i, int & _j)
: i{_i}
, j{_j}
{}
};
// 用{}调用构造函数
__lambda_8_11 f = __lambda_8_11{i, j};
// 调用()重载函数
f.operator()();
return 0;
}
注意:
(1)默认生成的operator()() const
是带有const的,如果你的lambda声明为mutable,那么函数将会变为operator()()
。
(2)怎么转化为一个函数指针,其实是在内部创建了一个static的返回函数,在里面直接返回了函数指针。
(3)模板的实现,是将里面的函数定义成了模板函数,来实现对应泛型的效果。
上面我们看到了每使用一个lambda表达式,就会生成一个class,因此C++标准对无状态的lambda表达式(即没有捕获任何外部的参数),那么它可以隐式的转化为函数指针,这样减少类的创建,减少性能开销。
void f(void(*)())
{
};
void g(void(&)())
{
};
int main()
{
f([] {});
g(*[] {});
return 0;
};
在C++14中引入广义捕获的概念,这使得在捕获列表中可以设置一个变量将表达式的值进行拷贝。
应用场景:
(1)对将要销毁的值在捕获列表中进行拷贝。
class A
{
private:
int value;
public:
A() : value(10) {}
auto fun()
{
auto b = [this]() -> int
{
return value;
};
return b;
}
};
// 返回销毁后的A对象的捕获列表
auto fun()
{
A a;
return a.fun();
}
// 因为A对象的销毁,此处访问会造成严重的内存泄漏
int main()
{
A a;
auto f = fun();
int i = f();
std::cout << i << std::endl;
};
输出结果如下:
而在C++14中 ,我们可以对this进行拷贝并返回,这样就不会出问题了。
class A
{
private:
int value;
public:
A() : value(10) {}
auto fun()
{
// 捕获列表中拷贝this对象,就算该对象被析构,lambda创建的这个类是一值存在的
auto b = [ca = *this]() -> int
{
return ca.value;
};
return b;
}
};
auto fun()
{
A a;
return a.fun();
}
int main()
{
A a;
auto f = fun();
int i = f();
std::cout << i << std::endl;
};
而在C++17中进行了进一步的优化,访问this只需要在捕获列表中使用*this
即可,而this已经不是原来的this了。
class A
{
private:
int value;
public:
A() : value(10) {}
auto fun()
{
// 在C++17中只使用*this即可
auto b = [*this]() -> int
{
return this->value;
};
return b;
}
};
正因为这个原因,在C++20中为了区分*this
和this
对语法的格式进行了要求:
/** =虽然可以捕获this,但是C++20强制要求显式捕获 */
[=]() -> int
{
return this->value;
};
/** 正确的格式 */
[=, this]() -> int
{
return this->value;
};
(2)在捕获列表中使用移动构造减少性能开销
std::string s = "test";
auto f = [x = std::move(s)] ()
{
// ......
};
(1)在参数列表中使用auto,达到泛型的效果。
auto f = [] (auto x)
{
return x;
};
(2)lambda表达式对模板的支持
C++20之后,在捕获列表之后加入
,达到泛型编程的作用。
auto f = [] <typename T> (std::vector<T> vector)
{
// .....
}
因为上面说了lambda表达式其实是一个函数指针,因此lambda表达式的默认构造函数和赋值构造函数都被删除了。在C++20中,允许了无状态的lambda表达式的默认构函数和默认赋值函数。
auto f = [](auto x, auto y)
{
return x > y;
};
/** map的比较中需要传入一个类型并作为模板传入模板 */
// 构造允许
std::map<std::string, int, decltype(f)> Map1, Map2;
// 赋值允许
Map2 = Map1;