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

MHe*_*bes 6 c++ lambda c++17

在下面的代码中:

#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;
}
Run Code Online (Sandbox Code Playgroud)

当使用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
Run Code Online (Sandbox Code Playgroud)

通过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
Run Code Online (Sandbox Code Playgroud)
  1. 我预计[=]捕获将创建 1 个tester. 为什么有几个副本立即被销毁?

  2. 为什么 MSVC 和 GCC 之间存在分歧?这是未定义的行为还是什么?

And*_*dyG 3

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

\n
\n

授权:以下内容全部正确:

\n
    \n
  • is_\xc2\xadconstructible_\xc2\xadv<decay_\xc2\xadt<F>, F>
  • \n
  • [...]
  • \n
\n
\n

is_\xc2\xadconstructible_\xc2\xadv<decay_\xc2\xadt<F>, F>是一样的is_copy_constructible(或者更确切地说,是相反的)。

\n

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

\n

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

\n

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

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

现场演示

\n

输出:

\n
constructor 0x7ffdfdf9d1e8\n  before lambda\n  thread start\nprint       0x7ffdfdf9d1e8\n  thread end\n  after join\ndestructor  0x7ffdfdf9d1e8\n
Run Code Online (Sandbox Code Playgroud)\n