2

When testing threads in C++11 I have created the following example:

#include <iostream>
#include <thread>

class Foo {
public:
    Foo(void) {
        std::cout << "Constructor called: " << this << std::endl;
    }
    ~Foo(void) {
        std::cout << "Destructor called: " << this << std::endl;
    }
    void operator()() const {
        std::cout << "Operatior called: " << this << std::endl;
    }
};

void test_normal(void) {
    std::cout << "====> Standard example:" << std::endl;
    Foo f;
}

void test_thread(void) {
    std::cout << "====> Thread example:" << std::endl;
    Foo f;
    std::thread t(f);
    t.detach();
}


int main(int argc, char **argv) 
{
    test_normal();
    test_thread();

    for(;;);
}

Which prints the following:

enter image description here

Why is the destructor called 6 times for the thread? And why does the thread report different memory locations?

EDIT When adding move and copy constructor output:

enter image description here

Praetorian
  • 106,671
  • 19
  • 240
  • 328
toeplitz
  • 777
  • 1
  • 8
  • 27

4 Answers4

7

The function object will be moved or copied. You didn't account in your output for any of these.

Dietmar Kühl
  • 150,225
  • 13
  • 225
  • 380
4

Add a copy constructor and move constructor to your class.

Foo(Foo const&) { std::cout << "Copy Constructor called: " << this << std::endl; }
Foo(Foo&&) { std::cout << "Move Constructor called: " << this << std::endl; }

Now if you run the code the output (on gcc 4.7.2) looks like this:

====> Standard example:
Constructor called: 0xbff696ff
Destructor called: 0xbff696ff
====> Thread example:
Constructor called: 0xbff696ff
Copy Constructor called: 0xbff696cf
Move Constructor called: 0x93a8dfc
Destructor called: 0xbff696cf
Destructor called: 0xbff696ff
Operator called: 0x93a8dfc
Destructor called: 0x93a8dfc

As you can see, the number of calls to the destructor matches the number of calls to the various constructors.

I suspect gcc manages to elide a few of the copy / move construction calls that MSVC seems to be making, so there are fewer calls to destructor than your example.


Furthermore, you can avoid the copy construction completely by std::moveing the Foo object to the thread constructor.

In test_thread change the thread construction line to

std::thread t(std::move(f));

Now the output looks like this:

====> Standard example:
Constructor called: 0xbfc23e2f
Destructor called: 0xbfc23e2f
====> Thread example:
Constructor called: 0xbfc23e2f
Move Constructor called: 0xbfc23dff
Move Constructor called: 0x9185dfc
Destructor called: 0xbfc23dff
Destructor called: 0xbfc23e2f
Operator called: 0x9185dfc
Destructor called: 0x9185dfc
Praetorian
  • 106,671
  • 19
  • 240
  • 328
  • 1
    I see. I added my output to the post now. Looks like I have 3 additional move constructor calls. And this is compiler related? – toeplitz Oct 19 '12 at 23:32
  • There will be one construction call when you create the `Foo` object, and a copy construction call when you pass it to the thread constructor. The remaining ones are implementation related. – Praetorian Oct 19 '12 at 23:36
2

Because your Foo is on the stack, not heap. This means that you allocate a new one inside test_thread, then it gets copied when you call std::thread(f) and again inside thread(f).

You would need to instead create a pointer to allocate on the heap, and pass that so that the object isn't copied each time, using the heap (new) to allocate it.

Jake
  • 199
  • 2
  • 5
  • im not sure I understand. I thought it would only be copied once? – toeplitz Oct 19 '12 at 23:16
  • It's copied when you pass the value into the thread constructor, and it's copied by the thread constructor to put it into the base. This means that you will delete first the copy in the test_thread and test_normal, then the copy inside the thread constructor and then the copy inside the thread itself. – Jake Oct 19 '12 at 23:22
  • I understand two destruction calls in the thread test. But there is 4 more?? – toeplitz Oct 19 '12 at 23:25
  • You don't have a proper copy or move constructor for your class. You see the extra ones because some are from the normal foo, and you actually have first a constructor, then a copy, then a move. That's 3. And you get at least 1 more for the basic output. – Jake Oct 19 '12 at 23:35
1

Compiler adds default move and copy constructors if you do not do it yourself, check this

https://ideone.com/wvctrl

#include <iostream>
#include <thread>

class Foo {
public:
    Foo(Foo&& f) {
        std::cout << "Constructor Foo&& called: " << this << std::endl;
    }
    Foo(const Foo& f) {
        std::cout << "Constructor const Foo& called: " << this << std::endl;
    }
    Foo(void) {
        std::cout << "Constructor called: " << this << std::endl;
    }
    ~Foo(void) {
        std::cout << "Destructor called: " << this << std::endl;
    }
    void operator()() const {
        std::cout << "Operatior called: " << this << std::endl;
    }
};

void test_normal(void) {
    std::cout << "====> Standard example:" << std::endl;
    Foo f;
}

void test_thread(void) {
    std::cout << "====> Thread example:" << std::endl;
    Foo f;
    std::thread t(f);
    t.detach();
}


int main(int argc, char **argv) 
{
    test_normal();
    test_thread();

    for(;;);
}

it shows that all ctors pair with dtors.

Also look into this SO:

Rule-of-Three becomes Rule-of-Five with C++11?

Community
  • 1
  • 1
marcinj
  • 48,511
  • 9
  • 79
  • 100