一例
考虑如下代码:
#include <iostream>
#include <sstream>
#include <string>
#include <thread>
using namespace std;
int get_count()
{
static int count = 0;
return ++count;
}
class task
{
public:
task(int data) : data_(data) {}
auto lazy_launch()
{
return [*this, count = get_count()]() mutable { // 这里使用*this按值捕获,如果直接捕获this,会有问题
ostringstream oss;
oss << "Done work " << data_ << " (No. " << count
<< ") in thread " << this_thread::get_id() << '\n';
msg_ = oss.str();
calculate();
};
}
void calculate()
{
this_thread::sleep_for(100ms);
cout << msg_;
}
private:
int data_;
string msg_;
};
int main()
{
task t(37);
thread t1{t.lazy_launch()};
thread t2{t.lazy_launch()};
t1.join();
t2.join();
return 0;
}运行结果:
yychi@siaphisprm02259 ~/PacketForwardAgent/service/src/main/cpp/build> ./01_lambda_capture
Done work 37 (No. 1) in thread 281473391817152
Done work 37 (No. 2) in thread 281473383424448
yychi@siaphisprm02259 ~/PacketForwardAgent/service/src/main/cpp/build> ./01_lambda_capture
Done work 37 (No. 1) in thread 281473675637184
Done work 37 (No. 2) in thread 281473667244480
yychi@siaphisprm02259 ~/PacketForwardAgent/service/src/main/cpp/build> ./01_lambda_capture
Done work 37 (No. 1) in thread 281473119936960
Done work 37 (No. 2) in thread 281473111544256
yychi@siaphisprm02259 ~/PacketForwardAgent/service/src/main/cpp/build> ./01_lambda_capture
Done work 37 (No. 2) in thread 281473806221760
Done work 37 (No. 1) in thread 281473814614464
yychi@siaphisprm02259 ~/PacketForwardAgent/service/src/main/cpp/build> ./01_lambda_capture
Done work 37 (No. 2) in thread 281473281548736
Done work 37 (No. 1) in thread 281473289941440
fine, ok.
如果把注释处改为按this指针捕获,则运行结果为:
yychi@siaphisprm02259 ~/PacketForwardAgent/service/src/main/cpp/build> ./01_lambda_capture
Done work 37 (No. 2) in thread 281473417372096
Done work 37 (No. 2) in thread 281473417372096
yychi@siaphisprm02259 ~/PacketForwardAgent/service/src/main/cpp/build> ./01_lambda_capture
Done work 37 (No. 1) in thread 281473528775104
Done work 37 (No. 1) in thread 281473528775104
yychi@siaphisprm02259 ~/PacketForwardAgent/service/src/main/cpp/build> ./01_lambda_capture
Done work 37 (No. 2) in thread 281473521451456
Done work 37 (No. 2) in thread 281473521451456
yychi@siaphisprm02259 ~/PacketForwardAgent/service/src/main/cpp/build> ./01_lambda_capture
Done work 37 (No. 1) in thread 281473544192448
Done work 37 (No. 1) in thread 281473544192448
很奇怪有没有?
如果按指针捕获,lambda表达式中对成员msg_的修改是同一处,后跑的线程会覆盖先跑的线程,所以打印出来都是一句。而按*this捕获,lambda表达式中已经对task做了拷贝,两个线程写的msg_不是同一个,因此可以正常输出。并且原来的task对象t.msg_并未被改变,改变的是lambda表达式捕获的对象副本。
又例
今天(2026/07/31)再来一个案例,考虑如下代码:
int main() {
atomic_int count{0};
auto& t = Timer::Inst();
constexpr int N = 100;
vector<thread> thds;
for (int i = 0; i < N; ++i) {
thds.emplace_back([&]{
t.Register([&]{
printf("[thread_%d]: %d\n", i, count.fetch_add(1, memory_order_relaxed));
}, 10);
});
}
for (auto& t : thds) t.join();
this_thread::sleep_for(50ms);
return 0;
}假设Timer是一个定时器类,Register注册了一个每 10ms 执行一次的定时器。思考一下上面代码会输出什么?上面代码会输出
thread_100: 0
thread_100: 1
...
thread_100: 99
再一次违反了直觉有没有?这同样展示了lambda捕获的陷阱。同样的,创建线程是,捕获了局部变量 i, 而且是按引用捕获。在 for 循环结束后,i 变为 100,由于是按引用捕获,所有线程的闭包对象中 i 也是 100. 而退出循环后,i 其实已经销毁。当定时器延迟 10ms 真正运行时,i 其实已经无效了。所以打印出啥也不奇怪。应该将 i 改为按值捕获。
for (int i = 0; i < N; ++i) {
thds.emplace_back([&, i]{ // i按值捕获
t.Register([&, i]{ // i按值捕获
printf("[thread_%d]: %d\n", i, count.fetch_add(1, memory_order_relaxed));
}, 10);
});
}