std::thread, конструктор и деструктор класса

При тестировании потоков в С++ 11 я создал следующий пример:

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

Что печатает следующее:

введите здесь описание изображения

Почему деструктор для потока вызывается 6 раз? И почему поток сообщает о разных местах памяти?

EDIT При добавлении выходных данных конструктора перемещения и копирования:

введите здесь описание изображения


person toeplitz    schedule 19.10.2012    source источник


Ответы (4)


Добавьте конструктор копирования и переместите конструктор в свой класс.

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

Теперь, если вы запустите код, результат (на gcc 4.7.2) будет выглядеть следующим образом:

====> 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

Как видите, количество вызовов деструктора совпадает с количеством вызовов различных конструкторов.

Я подозреваю, что gcc удается исключить несколько вызовов конструкции копирования/перемещения, которые, по-видимому, делает MSVC, поэтому вызовов деструктора меньше, чем в вашем примере.


Кроме того, вы можете полностью избежать создания копии, std::move передав объект Foo конструктору потока.

В test_thread измените линию построения резьбы на

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

Теперь вывод выглядит так:

====> 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
person Praetorian    schedule 19.10.2012
comment
Я понимаю. Я добавил свой вывод в пост сейчас. Похоже, у меня есть 3 дополнительных вызова конструктора перемещения. И это связано с компилятором? - person toeplitz; 20.10.2012
comment
Будет один вызов построения при создании объекта Foo и вызов построения копирования при передаче его конструктору потока. Остальные связаны с реализацией. - person Praetorian; 20.10.2012

Объект функции будет перемещен или скопирован. Вы не учли в своем выводе ни один из них.

person Dietmar Kühl    schedule 19.10.2012

Потому что ваш Foo находится в стеке, а не в куче. Это означает, что вы выделяете новый внутри test_thread, затем он копируется при вызове std::thread(f) и снова внутри thread(f).

Вместо этого вам нужно будет создать указатель для выделения в куче и передать его, чтобы объект не копировался каждый раз, используя кучу (новую) для его выделения.

person Jake    schedule 19.10.2012
comment
я не уверен, что понимаю. Я думал, что это будет скопировано только один раз? - person toeplitz; 20.10.2012
comment
Он копируется, когда вы передаете значение в конструктор потока, и копируется конструктором потока, чтобы поместить его в базу. Это означает, что сначала вы удалите копию в test_thread и test_normal, затем копию внутри конструктора потока, а затем копию внутри самого потока. - person Jake; 20.10.2012
comment
Я понимаю два вызова уничтожения в тесте потока. а есть еще 4?? - person toeplitz; 20.10.2012
comment
У вас нет подходящего конструктора копирования или перемещения для вашего класса. Вы видите лишние, потому что некоторые из них из обычного foo, и на самом деле у вас есть сначала конструктор, затем копия, затем перемещение. Это 3. И вы получаете как минимум еще 1 для основного вывода. - person Jake; 20.10.2012

Компилятор добавляет конструкторы перемещения и копирования по умолчанию, если вы не делаете этого сами, проверьте это

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(;;);
}

это показывает, что все ctors соединяются с dtors.

Также посмотрите на этот SO:

Правило трех становится правилом пяти с C+ +11?

person marcinj    schedule 19.10.2012