向 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的
时候将会使用一个已经销毁的对象的成员。

### C++ Lambda 函数使用指南 #### 定义与基本语法 Lambda 表达式提供了一种简洁的方式来定义匿名函数对象。其一般形式如下: ```cpp [capture](parameters) -> return_type { // function body } ``` 捕获列表 `[capture]` 用于指定 lambda 是否可以访问外部变量以及如何访问这些变量。 参数列表 `(parameters)` 和返回类型 `-> return_type` 是可选的,如果省略返回类型,则编译器会自动推导。 #### 捕获列表详解 捕获列表决定了哪些局部变量可以在 lambda 内部被访问及其方式: - **空捕获列表**:不捕捉任何外部变量。 - **按值捕获**:通过值复制的方式获取外部变量副本,如 `[x]` 或者 `[=]`(表示所有能捕获到的局部变量都按值传递)[^2]。 - **按引用捕获**:直接操作外部变量本身而不是它的拷贝,比如 `[&y]` 或者 `[&]`(意味着所有能够被捕获的局部变量均按照引用方式进行处理)。需要注意的是,在这种情况下要特别小心生命周期管理问题以免造成悬垂指针或未定义行为。 - **混合模式**:允许同时采用上述两种方法来分别对待不同类型的变量,例如 `[this,x,&z]` 可以让当前实例成员函数内的 this 指向的对象参与进来并带上名为 x 的局部变量的一个副本来作为实参入;而 z 则是以引用的形式加入其中以便修改原始数据项的内容。 #### 实际应用案例分析 考虑这样一个场景——我们需要创建一个生成偶数序列的生成器,并将其应用于容器填充过程之中。这里给出具体实现代码片段: ```cpp auto gen = [i = 0]() mutable { i += 2; return i; }; std::vector<int> v(7); // 填充除了第一个和最后一个位置外的所有元素 std::generate(std::next(v.begin()), std::prev(v.end()), gen); for (const int& elem : v){ std::cout << elem << " "; } // 输出结果可能是类似于:"0 2 4 6 8 0 0" ``` 此例子展示了如何利用带有初始状态初始化表达式lambda 来构建自增计数器逻辑,并配合 STL 算法完成特定任务的操作流程。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值