向 lambda 传递 this的拷贝

向 lambda 传递 this的拷贝

当在非静态成员函数里使用 lambda时,你不能隐式获取对该对象成员的使用权。也就是说,如果你不捕
获this的话你将不能在 lambda里使用该对象的任何成员(即使你用 this>来访问也不行):
class C {
private:
std::string name;
public:
...
void foo() {
auto l1 = [] { std::cout << name << '\n'; }; // ERROR
auto l2 = [] { std::cout << this>name << '\n'; }; // ERROR
...
}
};

在C++11和 C++14里,你可以通过值或引用捕获 this:

class C {
private:
std::string name;
public:
...
void foo() {
auto l1 = [this] { std::cout << name << '\n'; }; // OK
auto l2 = [=] { std::cout << name << '\n'; }; // OK
auto l3 = [&] { std::cout << name << '\n'; }; // OK
...
}
};

向lambda传递 this的拷贝
然而,问题是即使是用拷贝的方式捕获this实质上获得的也是引用(因为只会拷贝this指针)。当lambda的生
命周期比该对象的生命周期更长的时候,调用这样的函数就可能导致问题。比如一个极端的例子是在 lambda中
开启一个新的线程来完成某些任务, 调用新线程时正确的做法是传递整个对象的拷贝来避免并发和生存周期的
问题,而不是传递该对象的引用。另外有时候你可能只是简单的想向 lambda传递当前对象的拷贝。

自从 C++14 起有了一个解决方案,但可读性和实际效果都比较差:

class C {
private:
std::string name;
public:
...
void foo() {
auto l1 = [thisCopy=*this] { std::cout << thisCopy.name << '\n'; };
...
}
};

例如,当使用了 =或者 &捕获了其他对象的时候你可能会在不经意间使用 this:

auto l1 = [&, thisCopy=*this] {
thisCopy.name = "new name";
std::cout << name << '\n'; // OOPS: 仍 然 使 用 了 原 来 的name
};
//自从 C++17 起,你可以通过 *this显式地捕获当前对象的拷贝:
class C {
private:
std::string name;
public:
...
void foo() {
auto l1 = [*this] { std::cout << name << '\n'; };
...
}
};

这里,捕获 *this意味着该 lambda生成的闭包将存储当前对象的一份拷贝。
你仍然可以在捕获 *this的同时捕获其他对象,只要没有多个 this的矛盾:

auto l2 = [&, *this] { ... }; // OK
auto l3 = [this, *this] { ... }; // ERROR

这里有一个完整的例子:

#include <iostream>
#include <string>
#include <thread>

class Data {
private:
std::string name;
public:
Data(const std::string& s) : name(s) {
}
auto startThreadWithCopyOfThis() const {
// 开 启 并 返 回 新 线 程, 新 线 程 将 在3秒 后 使 用this:
using namespace std::literals;
std::thread t([*this] {
std::this_thread::sleep_for(3s);
std::cout << name << '\n';
});
return t;
}
};
int main()
{
std::thread t;
{
Data d{"c1"};
t = d.startThreadWithCopyOfThis();
} // d不 再 有 效
t.join();
}

lambda里捕获了 *this,所以传递进 lambda的是一份拷贝。因此,即使在 d被销毁之后使用捕获的对象也没有
问题。
如果我们使用[this]、[=]或者 [&]捕获 this,那么新线程将会陷入未定义行为,因为当线程中打印name的
时候将会使用一个已经销毁的对象的成员。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值