
一句话概括:
在定义 lambda 的 captures 时,可以通过 [*this]这种形式,将当前 this 所指向的 object,
以 value-copy 的方式拷贝下来,并在 lambda 的表达式中使用拷贝得到的 object 成员变量值。
完整 demo 代码参见 gitee.com 上的公开代码仓库 :
[lambda_capture_of_this_by_value]
对于在一个 non-static 成员函数中声明的 lambda 表达式,如果需要访问当前 object 的成员变量,在 C++17 之前,
我们需要 capture 代表当前对象的 ‘this’ 指针,再通过 ‘this’ 去访问当前 object 的成员变量:有三种 capture 的方式,
即 [=]、[&]、[this]; 但这三种方式,都只能让 lambda 表达式中访问当前 object 的成员变量方式,全都为 by reference,
做不到真正的 by value copy,我们来看一下例子代码:
/* to capture (*this) */
struct S {
S(int i) : m_i(i) {};
int m_i;
// all of get_a, get_b and get_c, capture (*this) by reference.
auto get_a() {
// implicit capture of (*this) by reference.
auto a = [&] { return m_i; }; // transformed to (*this).m_i
return a;
}
auto get_b() {
// implicit capture of (*this) by reference, even implicit capture the used automatic variables by copy, i.e. '='.
auto b = [=] { return m_i; }; // transformed to (*this).m_i
return b;
}
auto get_c() {
// explicit capture of (this) by copy, then capture of (*this) by reference.
auto c = [this] { return m_i; }; // transformed to (*this).m_i
return c;
}
}
int main(){
// test capture of (*this).
auto s = S(10);
// capture of (*this) by reference in a, b, c.
auto a = s.get_a();
auto b = s.get_b();
auto c = s.get_c();
s.m_i = 100;
// s.m_i value changed to 100, so do the return value of a(), b(), and c().
assert(a() == 100 and b() == 100 and c() == 100);
s.m_i = 200;
// s.m_i value changed to 200, so do the return value of a(), b(), and c().
assert(a() == 200 and b() == 200 and c() == 200);
return 0;
}
可以看到,在 struct S 中的 non-static 成员函数 get_a(), get_b(), get_c() 中定义的 lambda,访问当前 object ( 代码中即 s )
的成员变量 m_i 的方式,都为 by reference; 当 s.m_i 的值改变的时候,lambda 表达式回的值,也跟着改变;
lambda 作为异步和并发编程的基础 *** 作,如果只能对当前 object 进行 by reference 的 capture 的话,容易造成我们编程时的各种“陷阱”,比如下面的例子:
class Worker {
public:
Worker(int v) : m_i(v){};
auto get_job_capture_this_by_ref(){
// implicit capture of (*this) by reference.
return std::async(std::launch::async, [=]{ return m_i * m_i;});
}
private:
int m_i;
};
auto dispatch_job_will_cause_error(int v){
auto worker = Worker(v);
return worker.get_job_capture_this_by_ref();
// NOTE: the lambda associated with the returned std::future
// has an implicit capture of 'this' pointer which is invalid now.
}
int main(){
auto job1 = dispatch_job_will_cause_error(100);
std::this_thread::yield();
auto ret1 = job1.get();
assert(ret1 != 100 * 100 );
std::cout << "value of ret1: " << ret1 << std::endl;
}
从代码运行打印来看,ret1 已经是一个异常值:
value of ret1: 1073676289
分析一下:在 dispatch_job_will_cause_error(int v)函数中,我们实例化了一个worker对象,然后通过调用
worker.get_job_capture_this_by_ref()得到了一个 std::future 对象,
该 future 对象关联了一个在 Worker::get_job_capture_this_by_ref()中定义的 lambda:
[=]{ return m_i * m_i;}
该 lambda 使用 [=]的方式,实际上对上述 worker 对象的 capture by reference;当代码执行从
dispatch_job_will_cause_error(int v)函数中退出时,worker 被析构,但是在 int main()函数中,当代码执行到
auto ret1 = job1.get();时,上述 lambda 会直接对 worker 对象中的成员变量 m_i 进行访问,但是因为 worker 对象已经被析构,所以
读取到的是一个内存中的随机值,最后得到 1073676289这个异常值。
所以,为了让 lambda 更好的适应异步编程的情况,减少类似的编程错误,C++17 增加了一种新的 capture 的方式:
*this: 通过 by value copy 的方式,capture 当前的 object, 比如,下面这个 lambda 表达式:
[*this](){ return m_i;};
会被转换成等同于下面这种形式:
[ _tmp=*this ](){ return _tmp.m_i;}
即,会生成一个临时对象 _tmp, 该临时对象拷贝自 *this,然后在 lambda 表达式中,所有对当前对象成员的访问,都转换成对临时对象 _tmp 中
对等成员的访问。
来看一个简单的例子:
struct S {
S(int i):m_i(i) {};
// since C++17, we can capture (*this) by value copy, explicitly.
auto get_d(){
auto d = [*this](){ return m_i;}; // transformed to [ _tmp=*this ](){ return _tmp.m_i;}
return d;
}
};
int main(){
// test capture of (*this).
auto s = S(88);
// capture of (*this) by value copy.
auto d = s.get_d();
s.m_i = 400;
assert(d() == 88);
}
可以看到,在 struct S 中的 non-static 成员函数 get_d() 中定义的 lambda,访问当前 object ( 代码中即 s ) 的成员变量 m_i 的方式,
即为 by value copy 的方式,当 s.m_i 的值改变的时候,lambda 表达式返回的值,不会跟着改变,保持为 lambda 表达式被解析时的 88;
再回到上面异步编程的例子,通过使用 *this这种 capture by value copy 的方式,我们可以得到正确的结果:
class Worker {
public:
Worker(int v) : m_i(v){};
auto get_job_capture_this_by_value_copy(){
//since C++17: capture of (*this) by value copy.
return std::async(std::launch::async, [*this]{ return m_i * m_i;});
}
private:
int m_i;
};
auto dispatch_job_correctly(int v){
auto worker = Worker(v);
// the lambda associated with the returned std::future
// has a copy of 'worker' object.
return worker.get_job_capture_this_by_value_copy();
}
int main(){
auto job2 = dispatch_job_correctly(200);
std::this_thread::yield();
auto ret2 = job2.get();
assert(ret2 == 200 * 200 );
std::cout << "value of ret2: " << ret2 << std::endl;
}
运行上述代码打印如下:
value of ret2: 40000
补充说明 : 其实从 C++14 开始,我们如果使用 by-copy capture with an initializer 的方式,也是可以实现 capture of (*this) by value copy 的效果:
class Worker {
int m_i;
auto get_job_capture_this_by_value_copy(){
// since C++14: by-copy capture of (*this) with initializer
return std::async(std::launch::async, [=, tmp_obj=*this]{
// NOTE: every reference to a member of Worker, must be qualified with 'tmp_obj.'
// Any mistaken omissions will silently fail as reference via 'this->',that is,still
// capture by reference.
return tmp_obj.m_i * tmp_obj.m_i;
;});
}
};
但 [=, tmp_obj=*this]这种形式,容易引入因疏忽造成的 bug: 在 lambda 表达式中,对每一个 Worker 成员变量的引用,
都要显示的加上 ‘tmp_obj.’ 前缀,如果忽略了该前缀,则实际的效果又成为了 ‘this->’ 这种引用的方式,这对代码的维护带来隐患,
特别是 lambda 的实现比较复杂的情况下。
欢迎分享,转载请注明来源:内存溢出
微信扫一扫
支付宝扫一扫
评论列表(0条)