2 lambda 捕获 *this by Value 的动机
真正按值捕获 *this 允许在调用闭包的函数之前复制隐式声明的闭包。
2.1 lambda的异步调度
闭包的异步调度是并行性和并发性的基石。
当通过 std::async 或其他并发/并行分派机制从非静态成员函数内异步分派 lambda 时,不能按值捕获封闭的 *this 类。因此,当调度的 lambda 的未来(或其他句柄)超过原始类时,lambda 捕获的 this 指针是无效的。
class Work {
private:
int value ;
public:
Work() : value(42) {}
std::future<int> spawn()
{ return std::async( [=]()->int{ return value ; }); }
};
std::future<int> foo()
{
Work tmp ;
return tmp.spawn();
// The closure associated with the returned future
// has an implicit this pointer that is invalid.
}
int main()
{
std::future<int> f = foo();
f.wait();
// The following fails due to the
// originating class having been destroyed
assert( 42 == f.get() );
return 0 ;
}
2.2 向数据分派异步闭包
当前和未来专门针对并行性和并发性的硬件架构具有异构内存系统。例如,NUMA 区域、附加的加速器内存和内存处理 (PIM) 堆栈。在这些架构中,如果将闭包复制到它所操作的数据中,而不是将数据移入和移出闭包,则通常会显着提高性能。
例如,如果驻留在同一 NUMA 区域中的闭包的副本作用于该数据,则在跨 NUMA 区域的大数据上并行执行闭包的性能会更高。如果将(自包含)按值捕获的 lambda 闭包提供给并行调度,例如在并行技术规范中,则库可以在每个 NUMA 区域内创建该闭包的副本,以改善并行的数据局部性计算。再举一个例子,在执行之前,必须将分派到具有单独内存的附加加速器的闭包复制到加速器的内存。因此,当前和未来的架构需要能够将闭包复制到数据。
2.3 繁琐且容易出错的解决方法
针对此缺陷的一种潜在解决方法是显式捕获原始类的副本。
class Work {
private:
int value ;
public:
Work() : value(42) {}
std::future<int> spawn()
{
return std::async( [=,tmp=*this]()->int{ return tmp.value ; });
}
};
此解决方法有两个缺点。首先,this 指针也被捕获,这为错误引用 this-> 成员而不是 tmp 提供了重要机会。成员。其次,在现有代码中引入异步调度的 lambda 表达式既繁重又适得其反。考虑将非静态成员函数中的 for 循环替换为并行技术规范中的每个构造的情况。
class Work {
public:
void do_something() const {
// for ( int i = 0 ; i < N ; ++i )
foreach( Parallel , 0 , N , [=,tmp=*this]( int i )
{
// A modestly long loop body where
// every reference to a member must be modified
// for qualification with 'tmp.'
// Any mistaken omissions will silently fail
// as reference