【问题标题】:Why dtors of functors called twice (multitimes), when passed to a thread as the argument for Function?为什么函数的 dtors 被调用两次(多次),当作为函数的参数传递给线程时?
【发布时间】:2015-01-03 22:45:21
【问题描述】:

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

#include <utility>
#include <thread>
#include <iostream>

typedef struct foo{
    foo() = default;
    void operator()(int i) const {}
    ~foo(){std::cout<<"dtor\n";}
} foo;

typedef struct bar{
    bar() = default;
    bar(const bar&) {std::cout<<"copy\n";}
    bar(bar&&) {std::cout<<"move\n";}
    void operator()(const foo& f, int k) const {f(k);}
    ~bar(){std::cout<<"bar\n";}
} 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 是用户输入。

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

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

谢谢!

更新 它不一定是原始问题的两倍,请参阅下面的 Praetorian 的回答,其中涉及 3 次 dtor 呼叫和 2 次移动。

【问题讨论】:

    标签: c++ multithreading c++11 libstdc++ stdthread


    【解决方案1】:

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

    typedef struct foo{
        foo() = default;
        void operator()(int i) const {}
    //    ~foo(){std::cout<<"dtor\n";}
    } 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 this produces the output(注释是我的)

    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
    

    output from clang 是相同的。

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

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

    Live demo

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

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

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

    【讨论】:

    • 非常感谢@Praetorian。只是一个简短的说明:似乎在我的 gcc(Cygwin 端口 4.8.3)中只调用了 1 次移动和 2 次 dtors 调用,也类似于 clang 的行为。
    • @CloudyTrees 这是一个错误,我不小心将-fno-elide-constructors 传递给了 gcc,这导致了额外的移动构造。
    猜你喜欢
    • 2020-02-12
    • 1970-01-01
    • 2018-10-26
    • 1970-01-01
    • 2021-11-06
    • 1970-01-01
    • 1970-01-01
    • 2013-12-11
    相关资源
    最近更新 更多