为什么这个 lambda [=] 捕获会创建多个副本?

Why does this lambda [=] capture create several copies?

在下面的代码中:

#include <iostream>
#include <thread>

using namespace std;

class tester {
public:
    tester() { 
        cout << "constructor\t" << this << "\n"; 
    }
    tester(const tester& other) { 
        cout << "copy cons.\t" << this << "\n"; 
    }
    ~tester() { 
        cout << "destructor\t" << this << "\n"; 
    }

    void print() const { 
        cout << "print\t\t" << this << "\n"; 
    }
};

int main() {
    tester t;

    cout << "  before lambda\n";
    thread t2([=] {
        cout << "  thread start\n";
        t.print();
        cout << "  thread end\n";
    });

    t2.join();
    cout << "  after join" << endl;
    
    return 0;
}

使用 cl.exe 编译时(在 Windows 上)我得到以下信息:

constructor 012FFA93
  before lambda
copy cons.  012FFA92
copy cons.  014F6318
destructor  012FFA92
  thread start
print       014F6318
  thread end
destructor  014F6318
  after join
destructor  012FFA93

使用 g++(在 WSL 上)我得到:

constructor     0x7ffff5b2155e
  before lambda
copy cons.      0x7ffff5b2155f
copy cons.      0x7ffff5b21517
copy cons.      0x7fffedc630c8
destructor      0x7ffff5b21517
destructor      0x7ffff5b2155f
  thread start
print           0x7fffedc630c8
  thread end
destructor      0x7fffedc630c8
  after join
destructor      0x7ffff5b2155e
  1. 我预计 [=] 捕获会创建 tester 的 1 个副本。为什么有好几份立即销毁?

  2. 为什么MSVC和GCC会有分歧?这是未定义的行为还是什么?

标准要求传递给 std::thread 的构造函数的可调用对象是可有效复制构造的 ([thread.thread.constr])

Mandates: The following are all true:

  • is_­constructible_­v<decay_­t<F>, F>
  • [...]

is_­constructible_­v<decay_­t<F>, F>is_copy_constructible 相同(或者相反,相反)。

这是为了允许实现自由传递可调用对象,直到它到达被调用的位置。 (事实上​​,标准本身建议至少复制一次可调用对象。)

由于 lambda 被编译成一个小的 class 并重载了函数调用运算符(functor),每次复制 lambda 时,它都会创建一个捕获的 tester 个实例的副本。

如果您不希望复制发生,您可以在捕获列表中引用您的实例:

thread t2([&ref = t] {
    cout << "  thread start\n";
    ref.print();
    cout << "  thread end\n";
});

Live Demo

输出:

constructor 0x7ffdfdf9d1e8
  before lambda
  thread start
print       0x7ffdfdf9d1e8
  thread end
  after join
destructor  0x7ffdfdf9d1e8