一例

考虑如下代码:

#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);
  });
}