C ++ Cópia de construção de construto e assign questão
-
20-09-2019 - |
Pergunta
Aqui está um extrato do item 56 do livro "C ++ Gotchas":
Não é incomum ver uma simples inicialização de um objeto Y escrito de qualquer uma das três maneiras diferentes, como se fossem equivalentes.
Y a( 1066 );
Y b = Y(1066);
Y c = 1066;
De fato, todas essas três inicializações provavelmente resultarão no mesmo código de objeto que está sendo gerado, mas não são equivalentes. A inicialização de A é conhecida como inicialização direta e faz com precisão o que se poderia esperar. A inicialização é realizada através de uma invocação direta de y :: y (int).
As inicializações de B e C são mais complexas. Na verdade, eles são complexos demais. Estas são as duas inicializações de cópia. No caso da inicialização de B, estamos solicitando a criação de um temporário anônimo do tipo Y, inicializado com o valor 1066. Em seguida, usamos esse temporário anônimo como um parâmetro para o construtor de cópias para a classe Y para inicializar b. Finalmente, chamamos o destruidor do anônimo temporário.
Para testar isso, fiz uma classe simples com um membro de dados (programa anexado no final) e os resultados foram surpreendentes. Parece que, para o caso de C, o objeto foi construído pelo construtor de cópias e não como sugerido no livro.
Alguém sabe se o padrão de idioma mudou ou isso é simplesmente um recurso de otimização do compilador? Eu estava usando o Visual Studio 2008.
Exemplo de código:
#include <iostream>
class Widget
{
std::string name;
public:
// Constructor
Widget(std::string n) { name=n; std::cout << "Constructing Widget " << this->name << std::endl; }
// Copy constructor
Widget (const Widget& rhs) { std::cout << "Copy constructing Widget from " << rhs.name << std::endl; }
// Assignment operator
Widget& operator=(const Widget& rhs) { std::cout << "Assigning Widget from " << rhs.name << " to " << this->name << std::endl; return *this; }
};
int main(void)
{
// construct
Widget a("a");
// copy construct
Widget b(a);
// construct and assign
Widget c("c");
c = a;
// copy construct!
Widget d = a;
// construct!
Widget e = "e";
// construct and assign
Widget f = Widget("f");
return 0;
}
Resultado:
Constructing Widget a
Copy constructing Widget from a
Constructing Widget c
Assigning Widget from a to c
Copy constructing Widget from a
Constructing Widget e
Constructing Widget f
Copy constructing Widget from f
Fiquei muito surpreso com os resultados da construção de d e e. Para ser mais preciso, eu esperava que um objeto vazio fosse criado e, em seguida, um objeto a ser criado e atribuído ao objeto vazio. Na prática, os objetos foram criados pelo construtor de cópias.
Solução
A sintaxe
X a = b;
onde A e B são do tipo X sempre significava construção de cópias. Quaisquer variantes, como:
X a = X();
são usados, não há nenhuma tarefa acontecendo e nunca foi. Construir e atribuir seria algo como:
X a;
a = X();
Outras dicas
O compilador tem permissão para otimizar casos b
e c
para ser o mesmo que a
. Além disso, as chamadas do operador de construção e tarefa de cópia podem ser totalmente eliminadas pelo compilador, então o que você vê não é necessariamente o mesmo com diferentes compiladores ou mesmo configurações de compilador.
A partir de C ++ 17, todos os três são equivalente (a menos que Y::Y(int)
é explicit
, o que simplesmente não permitiria c
) por causa do que costuma ser chamado Cópia obrigatória Elision.
Até Y c = 1066;
cria apenas um Y
objeto porque o resultado da conversão implícita para Y
é um Prvvalue usado para inicializar c
em vez de criar um temporário.