Encontre o trecho de código abaixo:
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;
}
A saída que estou recebendo é:
Constructed : 0x7ffe27d1b0a4
Destroyed : 0x7ffe27d1b06c
Thread is joining...
Thread running at : 11
Destroyed : 0x2029c28
x : 1
Destroyed : 0x7ffe27d1b0a4
Estou confuso como os destruidores com o endereço 0x7ffe27d1b06c e 0x2029c28 foram chamados e nenhum construtor foi chamado? Enquanto o primeiro e o último construtor e destruidor, respectivamente, são do objeto que eu criei.
c++
multithreading
destructor
SHAHBAZ
fonte
fonte
Respostas:
Você está faltando instrumentar a cópia-construção e mover a construção. Uma simples modificação no seu programa fornecerá evidências de que as construções estão ocorrendo.
Copiar Construtor
Saída (endereços variam)
Copiar Construtor e Mover Construtor
Se você fornecer um gerenciador, será preferido para pelo menos uma dessas cópias:
Saída (endereços variam)
Referência agrupada
Se você deseja evitar essas cópias, pode agrupar sua chamada em um wrapper de referência (
std::ref
). Como você deseja utilizart
após a conclusão da peça de rosqueamento, isso é viável para a sua situação. Na prática, você deve ter muito cuidado ao encadear referências a objetos de chamada, pois a vida útil do objeto deve estender pelo menos o tempo que o encadeamento que utiliza a referência.Saída (endereços variam)
Observe que, embora eu tenha mantido as sobrecargas de copy-ctor e move-ctor, nenhuma delas foi chamada, pois o wrapper de referência agora é a coisa que está sendo copiada / movida; não é a coisa que ele faz referência. Além disso, essa abordagem final fornece o que você provavelmente estava procurando;
t.x
de voltamain
é, de fato, modificado para11
. Não estava nas tentativas anteriores. No entanto, não posso enfatizar isso o suficiente: tenha cuidado ao fazer isso . A vida útil do objeto é crítica .Mover, e nada mais
Por fim, se você não tem interesse em reter
t
como no seu exemplo, pode usar a semântica de movimentação para enviar a instância diretamente para o encadeamento, movendo-se ao longo do caminho.Saída (endereços variam)
Aqui você pode ver que o objeto é criado, a referência rvalue para o mesmo e enviada diretamente para
std::thread::thread()
, onde é movida novamente para seu local de descanso final, pertencente ao encadeamento daquele ponto em diante. Não há copiadores envolvidos. Os dtors reais são contra duas conchas e o objeto concreto de destino final.fonte
Quanto à sua pergunta adicional publicada nos comentários:
O construtor de
std::thread
primeiro cria uma cópia do seu primeiro argumento (bydecay_copy
) - é aí que o construtor de cópia é chamado. (Observe que, no caso de um argumento rvalue , comothread t1{std::move(t)};
orthread t1{tFunc{}};
, move o construtor seria chamado.)O resultado de
decay_copy
é um temporário que reside na pilha. No entanto, comodecay_copy
é executado por um encadeamento de chamada , esse temporário reside em sua pilha e é destruído no final dostd::thread::thread
construtor. Conseqüentemente, o próprio temporário não pode ser usado por um novo thread criado diretamente.Para "passar" o functor para o novo encadeamento, um novo objeto precisa ser criado em outro lugar , e é aqui que o construtor de movimentação é chamado. (Se não existisse, o construtor de cópias seria invocado.)
Observe que podemos nos perguntar por que a materialização temporária adiada não é aplicada aqui. Por exemplo, nesta demonstração ao vivo , apenas um construtor é chamado em vez de dois. Acredito que alguns detalhes internos da implementação da biblioteca C ++ Standard dificultem a otimização a ser aplicada ao
std::thread
construtor.fonte