为什么需要初始化捕获?
在C++11中,Lambda表达式的捕获列表有一定的局限性:
- 只能捕获当前作用域内存在的变量(通过值 [=] 或引用 [&])。
- 只能直接捕获变量名,无法在捕获时执行表达式(比如移动操作、调用函数初始化等)。
这导致了一些问题,例如:
- 无法移动捕获(Move Capture):无法轻易地将一个只能移动(move-only)的类型(如 std::unique_ptr)捕获到Lambda中。在C++11中,这需要通过 std::bind 等技巧来实现,非常繁琐。
- 无法用任意表达式初始化捕获成员:你不能在捕获一个成员变量时,先对其进行一些计算再捕获结果。
C++14的初始化捕获就是为了解决这些问题而生的。它允许你使用任意表达式来初始化Lambda的捕获成员,从而极大地增强了灵活性和表现力。
语法格式
[数据成员 = 初始化表达式](参数列表) -> 返回值类型
{
函数体
}
// 在捕获时指定一个新名字
[新变量名 = 已存在变量名](参数列表) -> 返回值类型
{
函数体
}
具体用法和示例
移动捕获(Move Capture)
在C++11中要实现移动捕获,需要借助 std::bind 方法,例如:
std::unique_ptr<A> ptr = std::make_unique<A>();
auto cbk = std::bind([](const std::unique_ptr<A>& my_ptr)
{
my_ptr->do_something();
}, std::move(ptr));
// 注意:执行 std::move(ptr) 后,外部的 ptr 为 empty
C++14的优雅做法:
std::unique_ptr<A> ptr = std::make_unique<A>();
auto cbk = [my_ptr = std::move(ptr)]()
{
my_ptr->do_something();
};
// 注意:执行 std::move(ptr) 后,外部的 ptr 为 empty
按值捕获并重命名
有时想用一个更清晰的名字来捕获变量,可以对捕获的变量重命名:
std::string very_long_descriptive_external_name = "Hello";
// 捕获外部变量,但在Lambda内部使用一个简短的名字
auto lambda = [greeting = very_long_descriptive_external_name]
{
std::cout << greeting << std::endl; // 内部使用 `greeting`,更简洁
};
用表达式初始化捕获成员
可以在初始化表达式中进行计算:
int x = 10;
int y = 20;
// 捕获的不是 x 或 y,而是 x + y 的计算结果
auto lambda = [sum = x + y]
{
std::cout << "The sum is: " << sum << std::endl; // 输出 30
};
// 甚至可以用函数返回值来初始化
auto get_value = []() -> int { return 42; };
auto lambda2 = [value = get_value()] // 调用函数初始化
{
std::cout << value << std::endl;
};
捕获成员变量的“快照”
在非静态成员函数中,直接捕获成员变量实际上捕获的是 this 指针。如果Lambda的生命周期比对象长,这会导致悬空指针(dangling pointer)。使用初始化捕获可以捕获成员变量的一个副本或移动后的状态,从而避免这个问题。
C++11方式:
class MyClass
{
std::vector<int> m_data;
void register_callback()
{
// 危险!捕获了 `this`,如果对象被销毁,Lambda再运行就会出错
auto bad_lambda = [this]()
{
use(m_data);
};
}
};
安全的C++14方式:
class MyClass
{
std::vector<int> m_data;
void register_callback()
{
// 安全!将 m_data 移动捕获到Lambda内部,与原对象生命周期解耦
auto safe_lambda = [data = std::move(m_data)]()
{
use(data);
};
// 或者,如果你不想移动原数据,可以创建一个副本
/*
auto safe_lambda = [data_copy = m_data]()
{
use(data_copy);
};
*/
}
};