Пожалуйста, найдите фрагмент кода ниже:
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, а конструкторы не назывались? В то время как первый и последний конструктор и деструктор соответственно относятся к объекту, который я создал.
c++
multithreading
destructor
Shahbaz
источник
источник
Ответы:
Вам не хватает инструментов копирования-строительства и перемещения конструкции. Простая модификация вашей программы предоставит свидетельство того, где происходят конструкции.
Копировать конструктор
Вывод (адреса меняются)
Скопируйте конструктор и переместите конструктор
Если вы предоставите ctor для перемещения, предпочтительнее будет хотя бы одна из этих копий:
Вывод (адреса меняются)
Ссылка завернута
Если вы хотите избежать этих копий, вы можете обернуть свой отзыв в справочную оболочку (
std::ref
). Так как вы хотите использоватьt
после завершения потоковой обработки, это подходит для вашей ситуации. На практике вы должны быть очень осторожны при работе с ссылками на объекты вызова, так как время жизни объекта должно увеличиваться как минимум до тех пор, пока поток использует ссылку.Вывод (адреса меняются)
Обратите внимание, что хотя я сохранил перегрузки copy-ctor и move-ctor, ни один из них не был вызван, так как справочная обертка теперь является объектом копирования / перемещения; не то, на что оно ссылается. Кроме того, этот последний подход дает то, что вы, вероятно, искали;
t.x
обратноmain
, на самом деле, изменен на11
. Это было не в предыдущих попытках. Однако не могу этого подчеркнуть: будьте осторожны . Время жизни объекта имеет решающее значение .Двигаться, и ничего, кроме
Наконец, если вы не заинтересованы в сохранении,
t
как в своем примере, вы можете использовать семантику перемещения, чтобы отправить экземпляр прямо в поток, двигаясь по пути.Вывод (адреса меняются)
Здесь вы можете видеть, что объект создан, ссылка rvalue на said-same затем отправляется прямо туда
std::thread::thread()
, где он снова перемещается в свое последнее место отдыха, принадлежащее потоку с этой точки вперед. Копировщики не участвуют. Фактические дторы против двух оболочек и конкретного объекта конечного назначения.источник
Что касается вашего дополнительного вопроса, размещенного в комментариях:
Конструктор
std::thread
first создает копию своего первого аргумента (bydecay_copy
) - именно там вызывается конструктор copy . (Обратите внимание, что в случае аргумента rvalue , такого какthread t1{std::move(t)};
илиthread t1{tFunc{}};
, вместо этого будет вызываться конструктор перемещения .)Результатом
decay_copy
является временный объект, который находится в стеке. Однако, посколькуdecay_copy
выполняется вызывающим потоком , этот временный объект находится в его стеке и уничтожается в концеstd::thread::thread
конструктора. Следовательно, сам временный объект не может использоваться новым созданным потоком напрямую.Чтобы «передать» функтор в новый поток, новый объект должен быть создан где-то еще , и именно здесь вызывается конструктор перемещения . (Если он не существует, вместо этого будет вызван конструктор копирования.)
Обратите внимание, что мы можем задаться вопросом, почему отсроченная временная материализация здесь не применяется. Например, в этом демонстрационном примере вызывается только один конструктор вместо двух. Я полагаю, что некоторые внутренние детали реализации библиотеки C ++ Standard препятствуют оптимизации, применяемой для
std::thread
конструктора.источник