Search code examples
c++multithreadingc++11libstdc++stdthread

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


I'm having this question because of the following example:

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

which yields (both gcc and clang give the same result)

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

, where the 0 is user input.

So the dtor for bar--the argument for Function--is called twice after the tread finishes its job (for each iteration). What I don't understand, is why twice instead of just once(for making the copy)?

In addition, is it possible to avoid the copy, in case the functor itself holds non-copiable resources or is expensive to be copied?

Thank you!

UPDATE It's not necessarily twice as the original question asked, see Praetorian's answer below, where 3 dtor calls and 2 moves are involved.


Solution

  • You're passing an lvalue (b_1) to the std::thread constructor, so it'll copy that argument. I've modified your example so that it's easier to follow what's going on. Note that the while condition has been changed so that it only executes once.

    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";
        }
    }
    

    On gcc this produces the output (annotations are mine)

    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
    

    The output from clang is identical.

    There are a couple of different options if you want to avoid that copy. You can wrap the b_1 instance in std::reference_wrapper before passing it to std::thread.

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

    Live demo

    Or you can allow the std::thread constructor to move the b_1 instance.

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

    Live demo. In this case you'll incur the internal move construction performed by the std::thread implementation.