为什么函子的 dtor 在作为函数的参数传递给线程时调用两次(多次)

Why dtors of functors called twice (multitimes), when passed to a thread as the argument for Function?

本文关键字:调用 多次 线程 两次 参数传递 dtor 为什么 函数      更新时间:2023-10-16

由于以下示例,我有这个问题:

#include <utility>
#include <thread>
#include <iostream>
typedef struct foo{
    foo() = default;
    void operator()(int i) const {}
    ~foo(){std::cout<<"dtorn";}
} foo;
typedef struct bar{
    bar() = default;
    bar(const bar&) {std::cout<<"copyn";}
    bar(bar&&) {std::cout<<"moven";}
    void operator()(const foo& f, int k) const {f(k);}
    ~bar(){std::cout<<"barn";}
} bar;
int main(){
    foo f_1, f_2;
    bar b_1, b_2;
    int i(0), j(0);
    while(i++!=2){
            std::thread t(b_1, std::cref(f_1), i);
            b_2(f_2, j);
            t.join();
    }
    int dummy(0);
    std::cin >> dummy;
}

哪个产量(GCC 和 clang 给出相同的结果)

copy
move
bar
bar
copy
move
bar
bar
0
bar
bar
dtor
dtor

,其中 0 是用户输入。

因此,柱形的 dtor(函数的参数)在胎面完成其工作(每次迭代)后被调用两次。我不明白的是,为什么两次而不是一次(用于制作副本)?

此外,如果函子本身持有不可复制的资源或复制成本很高,是否可以避免复制?

谢谢!

更新它不一定是原始问题的两倍,请参阅下面的 Praetorian 的答案,其中涉及 3 个 dtor 调用和 2 个动作。

你正在将一个左值(b_1)传递给std::thread构造函数,所以它会复制该参数。我已经修改了您的示例,以便更容易了解正在发生的事情。请注意,while条件已更改,因此它只执行一次。

typedef struct foo{
    foo() = default;
    void operator()(int i) const {}
//    ~foo(){std::cout<<"dtorn";}
} foo;
typedef struct bar{
    bar()           {std::cout<<"bar      " << this << 'n';}
    bar(const bar&) {std::cout<<"bar copy " << this << 'n';}
    bar(bar&&)      {std::cout<<"bar move " << this << 'n';}
    void operator()(const foo& f, int k) const {f(k);}
    ~bar()          {std::cout<<"~bar     " << this << 'n';}
} bar;
int main(){
    foo f_1, f_2;
    bar b_1, b_2;
    int i(0), j(0);
    while(i++!=1){
            std::cout << "---- 1 ----n";
            std::thread t(b_1, std::cref(f_1), i);
            std::cout << "---- 2 ----n";
            b_2(f_2, j);
            t.join();
            std::cout << "---- 3 ----n";
    }
}

在 gcc 上,这会产生输出(注释是我的)

bar      0x7fffbcc2156c   // b_1 constructed
bar      0x7fffbcc2156d   // b_2 constructed
---- 1 ----
bar copy 0x7fffbcc21580   // std::thread ctor makes copy of b_1
bar move 0x162a038        // that copy is moved by std::thread impl
~bar     0x7fffbcc21580   // moved from object is destructed
---- 2 ----
~bar     0x162a038        // bar object owned by thread instance is destroyed
---- 3 ----
~bar     0x7fffbcc2156d   // b_2 is destroyed
~bar     0x7fffbcc2156c   // b_1 is destroyed

clang 的输出是相同的。

如果您想避免该副本,有几个不同的选项。您可以将b_1实例包装在 std::reference_wrapper 中,然后再将其传递给 std::thread

std::thread t(std::cref(b_1), std::cref(f_1), i);

现场演示

或者,您可以允许std::thread构造函数移动b_1实例。

std::thread t(std::move(b_1), std::cref(f_1), i);

现场演示。在这种情况下,您将产生std::thread实现执行的内部移动构造。