Потоки C ++ за допомогою об’єкта функції, як називаються кілька деструкторів, але не конструктори?


15

Знайдіть фрагмент коду нижче:

class tFunc{
    int x;
    public:
    tFunc(){
        cout<<"Constructed : "<<this<<endl;
        x = 1;
    }
    ~tFunc(){
        cout<<"Destroyed : "<<this<<endl;
    }
    void operator()(){
        x += 10;
        cout<<"Thread running at : "<<x<<endl;
    }
    int getX(){ return x; }
};

int main()
{
    tFunc t;
    thread t1(t);
    if(t1.joinable())
    {
        cout<<"Thread is joining..."<<endl;
        t1.join();
    }
    cout<<"x : "<<t.getX()<<endl;
    return 0;
}

Вихід, який я отримую:

Constructed : 0x7ffe27d1b0a4
Destroyed : 0x7ffe27d1b06c
Thread is joining...
Thread running at : 11
Destroyed : 0x2029c28
x : 1
Destroyed : 0x7ffe27d1b0a4

Мене бентежить, як викликали деструктори з адресою 0x7ffe27d1b06c та 0x2029c28, і не викликали конструкторів? Тоді як перший і останній конструктор і деструктор відповідно є створеним мною об'єктом.


11
Визначте і інструментуйте також ваш копіювальний і пересувний ctor.
WhozCraig

Ну, зрозумів. Оскільки я передаю об'єкт, викликається конструктор копій, чи я прав? Але коли називається конструктор ходу?
SHAHBAZ

Відповіді:


18

Вам не вистачає приладобудування копіювання та переміщення конструкції. Проста модифікація вашої програми надасть докази того, що саме там відбуваються спорудження.

Конструктор копіювання

#include <iostream>
#include <thread>
#include <functional>
using namespace std;

class tFunc{
    int x;
public:
    tFunc(){
        cout<<"Constructed : "<<this<<endl;
        x = 1;
    }
    tFunc(tFunc const& obj) : x(obj.x)
    {
        cout<<"Copy constructed : "<<this<< " (source=" << &obj << ')' << endl;
    }

    ~tFunc(){
        cout<<"Destroyed : "<<this<<endl;
    }

    void operator()(){
        x += 10;
        cout<<"Thread running at : "<<x<<endl;
    }
    int getX() const { return x; }
};

int main()
{
    tFunc t;
    thread t1{t};
    if(t1.joinable())
    {
        cout<<"Thread is joining..."<<endl;
        t1.join();
    }
    cout<<"x : "<<t.getX()<<endl;
    return 0;
}

Вихідні дані (адреси залежать)

Constructed : 0x104055020
Copy constructed : 0x104055160 (source=0x104055020)
Copy constructed : 0x602000008a38 (source=0x104055160)
Destroyed : 0x104055160
Thread running at : 11
Destroyed : 0x602000008a38
Thread is joining...
x : 1
Destroyed : 0x104055020

Копіювання конструктора та переміщення конструктора

Якщо ви надаєте ctor для переміщення, він буде кращим принаймні для однієї з цих копій:

#include <iostream>
#include <thread>
#include <functional>
using namespace std;

class tFunc{
    int x;
public:
    tFunc(){
        cout<<"Constructed : "<<this<<endl;
        x = 1;
    }
    tFunc(tFunc const& obj) : x(obj.x)
    {
        cout<<"Copy constructed : "<<this<< " (source=" << &obj << ')' << endl;
    }

    tFunc(tFunc&& obj) : x(obj.x)
    {
        cout<<"Move constructed : "<<this<< " (source=" << &obj << ')' << endl;
        obj.x = 0;
    }

    ~tFunc(){
        cout<<"Destroyed : "<<this<<endl;
    }

    void operator()(){
        x += 10;
        cout<<"Thread running at : "<<x<<endl;
    }
    int getX() const { return x; }
};

int main()
{
    tFunc t;
    thread t1{t};
    if(t1.joinable())
    {
        cout<<"Thread is joining..."<<endl;
        t1.join();
    }
    cout<<"x : "<<t.getX()<<endl;
    return 0;
}

Вихідні дані (адреси залежать)

Constructed : 0x104057020
Copy constructed : 0x104057160 (source=0x104057020)
Move constructed : 0x602000008a38 (source=0x104057160)
Destroyed : 0x104057160
Thread running at : 11
Destroyed : 0x602000008a38
Thread is joining...
x : 1
Destroyed : 0x104057020

Довідка загорнута

Якщо ви хочете уникнути цих копій, ви можете загортати свій дзвінок у обгортку посилання ( std::ref). Оскільки ви хочете використовувати tпісля завершення нарізки деталі, це є життєздатним для вашої ситуації. На практиці ви повинні бути дуже обережними при нанизуванні посилань на виклику об'єктів, оскільки термін експлуатації об'єкта повинен тривати принаймні до тих пір, поки потік використовує посилання.

#include <iostream>
#include <thread>
#include <functional>
using namespace std;

class tFunc{
    int x;
public:
    tFunc(){
        cout<<"Constructed : "<<this<<endl;
        x = 1;
    }
    tFunc(tFunc const& obj) : x(obj.x)
    {
        cout<<"Copy constructed : "<<this<< " (source=" << &obj << ')' << endl;
    }

    tFunc(tFunc&& obj) : x(obj.x)
    {
        cout<<"Move constructed : "<<this<< " (source=" << &obj << ')' << endl;
        obj.x = 0;
    }

    ~tFunc(){
        cout<<"Destroyed : "<<this<<endl;
    }

    void operator()(){
        x += 10;
        cout<<"Thread running at : "<<x<<endl;
    }
    int getX() const { return x; }
};

int main()
{
    tFunc t;
    thread t1{std::ref(t)}; // LOOK HERE
    if(t1.joinable())
    {
        cout<<"Thread is joining..."<<endl;
        t1.join();
    }
    cout<<"x : "<<t.getX()<<endl;
    return 0;
}

Вихідні дані (адреси залежать)

Constructed : 0x104057020
Thread is joining...
Thread running at : 11
x : 11
Destroyed : 0x104057020

Зверніть увагу, навіть якщо я зберігав перевантаження copy-ctor і move-ctor, жодного з них не викликали, оскільки опорна оболонка зараз копіюється / переміщується; не те, про що йдеться. Крім того, цей остаточний підхід забезпечує те, що ви, ймовірно, шукали; t.xПо mainсуті, фактично модифіковано до 11. Це не було в попередніх спробах. Однак не можу цього підкреслити достатньо: будьте обережні . Термін експлуатації об'єкта є критичним .


Рухайтеся, і нічого, але

Нарешті, якщо у вас немає інтересу зберегти так, tяк у вашому прикладі, ви можете використовувати семантику переміщення, щоб надіслати екземпляр прямо до потоку, рухаючись по шляху.

#include <iostream>
#include <thread>
#include <functional>
using namespace std;

class tFunc{
    int x;
public:
    tFunc(){
        cout<<"Constructed : "<<this<<endl;
        x = 1;
    }
    tFunc(tFunc const& obj) : x(obj.x)
    {
        cout<<"Copy constructed : "<<this<< " (source=" << &obj << ')' << endl;
    }

    tFunc(tFunc&& obj) : x(obj.x)
    {
        cout<<"Move constructed : "<<this<< " (source=" << &obj << ')' << endl;
        obj.x = 0;
    }

    ~tFunc(){
        cout<<"Destroyed : "<<this<<endl;
    }

    void operator()(){
        x += 10;
        cout<<"Thread running at : "<<x<<endl;
    }
    int getX() const { return x; }
};

int main()
{
    thread t1{tFunc()}; // LOOK HERE
    if(t1.joinable())
    {
        cout<<"Thread is joining..."<<endl;
        t1.join();
    }
    return 0;
}

Вихідні дані (адреси залежать)

Constructed : 0x104055040
Move constructed : 0x104055160 (source=0x104055040)
Move constructed : 0x602000008a38 (source=0x104055160)
Destroyed : 0x104055160
Destroyed : 0x104055040
Thread is joining...
Thread running at : 11
Destroyed : 0x602000008a38

Тут ви можете бачити, як створений об'єкт, посилання rvalue на сказане-те саме, потім відправляється прямо туди std::thread::thread(), де він знову переміщується до остаточного місця спокою, що належить потоку з цієї точки вперед. Жодних копій-копій не задіяно. Фактичні двигуни проти двох снарядів та конкретного об'єкта призначення.


5

Щодо вашого додаткового запитання, розміщеного в коментарях:

Коли називається конструктор переміщення?

Конструктор std::threadпершого створює копію свого першого аргументу (by decay_copy) - саме там називається конструктор копій . (Зверніть увагу, що у випадку аргументу rvalue , такого як thread t1{std::move(t)};або thread t1{tFunc{}};, конструктор переміщення буде викликаний замість цього.)

Результат decay_copy- тимчасовий, який знаходиться на стеці. Однак, оскільки decay_copyвиконується викличною ниткою , ця тимчасова перебуває на її стеці і знищується в кінці std::thread::threadконструктора. Отже, тимчасовий сам по собі новий створений потік безпосередньо не може бути використаний.

Щоб "передати" функтор в новий потік, новий об’єкт потрібно створити десь в іншому місці , і саме тут викликається конструктор переміщення . (Якби його не було, замість нього буде викликано конструктор копій.)


Зауважте, що ми можемо задатися питанням, чому відкладена тимчасова матеріалізація тут не застосовується. Наприклад, у цьому демонстраційному режимі лише один конструктор викликається замість двох. Я вважаю, що деякі внутрішні деталі впровадження впровадження бібліотеки стандарту C ++ заважають оптимізації застосовуватися для std::threadконструктора.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.