23. 捕获 *this:C++17 引入,安全捕获对象副本
聊到 Lambda 捕获,有个坑我踩过不止一次——捕获 this 指针。你想想看,Lambda 里用了成员变量,顺手写了 [this],结果对象析构了,Lambda 还在回调,直接野指针崩溃。C++17 终于给了我们一个优雅的解法:[*this]。
为什么需要捕获 *this?
先看一个经典翻车现场。我在项目里写过这样的代码:
class Downloader {
std::string url_;
public:
void start() {
// 发起异步回调
thread_pool.post([this]() {
// 危险!this 可能已经失效
process(url_);
});
}
void process(const std::string& url) { /* ... */ }
};
问题在哪?this 是个指针。Lambda 只保存了指针的值,不保证对象还活着。如果 Downloader 对象在回调执行前被销毁,this 就成了悬空指针。嗯,这就是典型的 use-after-free。
C++17 引入的 [*this] 捕获,说白了就是按值捕获整个对象的副本。Lambda 内部操作的是副本,跟原对象彻底解耦。安全,干净。
[*this] 的用法与原理
语法很简单:
class Downloader {
std::string url_;
public:
void start() {
thread_pool.post([*this]() {
// 安全!操作的是 *this 的副本
process(url_);
});
}
void process(const std::string& url) { /* ... */ }
};
这里 [*this] 会调用 Downloader 的拷贝构造函数,生成一个当前对象的快照。Lambda 内部访问的 url_ 实际上是副本的成员,跟原对象无关。
核心区别:
[this]— 捕获指针,对象销毁后悬空[*this]— 捕获副本,对象销毁后仍安全
什么时候该用 *this?
我个人习惯在以下场景优先考虑 [*this]:
- 异步回调:线程池、定时器、网络请求等延迟执行的回调
- 对象生命周期不确定:Lambda 可能比对象活得更久
- 状态快照:需要记录当前时刻的对象状态,后续不受原对象影响
但注意,[*this] 不是银弹。如果对象很大,拷贝成本可能很高。我曾经在一个图像处理类里用 [*this],结果每次回调都拷贝整个图像缓冲区,性能直接崩了。后来改成了只捕获需要的成员变量。
避坑指南
我曾经踩过的坑:
- 拷贝成本失控:对象里有大数组、智能指针管理的堆内存,拷贝可能很重。建议只捕获必要字段,或者用
std::shared_ptr共享数据。 - 多态失效:
[*this]会切片!如果通过基类指针调用,捕获的是基类部分的副本,虚函数调用会丢失派生类行为。我有个同事因此排查了一整天。 - const 成员函数:在 const 成员函数里用
[*this],捕获的副本也是 const 的,不能修改。需要 mutable 的话,得显式声明 Lambda 为 mutable。
代码示例对比
看个完整的对比:
#include <iostream>
#include <functional>
class Counter {
int value_ = 0;
public:
std::function<void()> getBadLambda() {
return [this]() {
// 危险!如果对象已销毁,value_ 是野指针
std::cout << "Bad: " << value_++ << "\n";
};
}
std::function<void()> getGoodLambda() {
return [*this]() mutable {
// 安全!操作的是副本
std::cout << "Good: " << value_++ << "\n";
};
}
void show() const {
std::cout << "Original value: " << value_ << "\n";
}
};
int main() {
auto bad = Counter().getBadLambda(); // 临时对象已销毁
// bad(); // 未定义行为!大概率崩溃
auto good = Counter().getGoodLambda();
good(); // 安全,输出 Good: 0
good(); // 输出 Good: 1(副本独立递增)
// 原对象早已销毁,但 Lambda 依然安全
return 0;
}
运行结果:
Good: 0
Good: 1
注意 getBadLambda 那行,如果取消注释,程序大概率直接崩。而 getGoodLambda 即使原对象已经析构,Lambda 依然能正常执行。
知识结构图
总结
[*this] 是 C++17 给 Lambda 捕获机制打的一个漂亮补丁。它让我们在异步编程中少了一个常见的崩溃源。我个人建议:只要 Lambda 的生命周期可能超过对象本身,优先用 [*this]。但别忘了评估拷贝成本,必要时用 std::shared_ptr 或捕获特定成员来替代。
小技巧:如果你不确定该用 [this] 还是 [*this],问自己一个问题——「这个 Lambda 会不会在对象销毁后执行?」。答案是「会」或「不确定」,就用 [*this]。答案是「绝对不会」,才考虑 [this]。