Diferença entre o construtor de cópias e o operador de atribuição

Como a pergunta não está perguntando, vou abandonar intencionalmente a semântica "mover" na discussão a seguir.

Observe também que, como o operador de atribuição não precisa ser um operador de atribuição de cópias, ele pode ser um operador de atribuição para qualquer outro tipo, portanto, os seguintes limites limitam apenas o operador de atribuição de cópias a comparar com o construtor de cópias.

1. Definições

Copiar construtor

Um construtor não modelo para a classe X é um construtor de cópia se seu primeiro parâmetro for do tipo X &, const X &, volátil X & ou const volátil X & e não houver outros parâmetros ou todos os outros parâmetros tiverem argumentos padrão.

Portanto, o seguinte é um construtor de cópia, struct X {X (const X &, int = 1); };

Operador Copiar atribuição

Um operador de atribuição de cópia declarado pelo usuário X :: operator = é uma função membro não estática e não-modelo da classe X com exatamente um parâmetro do tipo X, X &, const X &, X volátil ou X volátil ou X volátil constante &

Portanto, a primeira diferença é que o operador de atribuição de cópia, por definição, pode assumir o parâmetro X, mas o construtor de cópia, por definição, não pode.

2. Geração implícita

O compilador irá gerar implicitamente o construtor de cópias e o operador de atribuição de cópias, se não declarado pelo usuário.

Copiar construtor

Se a definição de classe não declarar explicitamente um construtor de cópia, um será declarado implicitamente. Se a definição de classe declara um construtor de movimentação ou um operador de atribuição de movimentação, o construtor de cópia declarado implicitamente é definido como excluído; caso contrário, é definido como padrão (8.4). O último caso será descontinuado se a classe tiver um operador de atribuição de cópia declarada pelo usuário ou um destruidor declarado pelo usuário.
O construtor de cópias declarado implicitamente para uma classe X terá o formato X :: X (const X &) se cada subobjeto potencialmente construído de um tipo de classe M (ou matriz do mesmo) possuir um construtor de cópias cujo primeiro parâmetro seja do tipo const M & ou const & volátil M &.
Caso contrário, o construtor de cópias declarado implicitamente terá o formato X :: X (X &)

Operador de atribuição de cópia

Se a definição de classe não declarar explicitamente um operador de atribuição de cópia, um será declarado implicitamente. Se a definição de classe declara um construtor de movimentação ou um operador de atribuição de movimentação, o operador de atribuição de cópia declarado implicitamente é definido como excluído; caso contrário, é definido como padrão (8.4). O último caso será preterido se a classe tiver um construtor de cópias declarado pelo usuário ou um destruidor declarado pelo usuário. O operador de atribuição de cópia declarado implicitamente para uma classe X terá o formato
X & X :: operator = (const X &) if (18.1) - cada classe de base direta B de X possui um operador de atribuição de cópias cujo parâmetro é do tipo const B &, const volátil B & ou B e (18.2) - para todos os membros de dados estáticos de X que são de uma classe de tipo M (ou matriz dos mesmos), cada um desses tipos de classe possui um operador de atribuição de cópia cujo parâmetro é do tipo const M &, const volátil M & ou M.121
Caso contrário, o operador de atribuição de cópia declarado implicitamente terá o formato
X e X :: operador = (X e)

É interessante notar que, o operador de atribuição de cópia gerado pelo compilador será descontinuado se a classe tiver um construtor de cópias definido pelo usuário e vice-versa.

3. Quando ligar para qual

É importante entender em que situação o construtor de cópias é chamado e em que situação, o operador de atribuição de cópias é chamado.

O construtor de cópia é, obviamente, chamado durante a construção do objeto (inicialização).

Primeiro, definimos algumas definições para inicialização.

Inicialização direta

A inicialização que ocorre nas formas T x (a); T x {a}; bem como em novas expressões (5.3.4), expressões static_cast (5.2.9), conversões de tipo de notação funcional (5.2.3), inicializadores de mem (12.6.2) e a forma de lista de init braced de uma condição é chamado de inicialização direta.

Inicialização de cópia (não muito relacionada ao construtor de cópias).

A inicialização que ocorre na forma = de uma chave ou condição de inicializador igual ou igual a (6.4), bem como na passagem de argumentos, retorno de função, lançando uma exceção (15.1), manipulando uma exceção (15.3) e membro agregado inicialização (8.5.1), é chamada cópia-inicialização.

Portanto, durante a inicialização e quando o formulário = é usado, ele é definido como inicialização de cópia.

Se a inicialização for de inicialização direta ou de inicialização de cópia, em que a versão não qualificada de cv do tipo de origem é a mesma classe que, ou uma classe derivada, da classe do destino, os construtores serão considerados. Os construtores aplicáveis ​​são enumerados (13.3.1.3) e o melhor é escolhido através da resolução de sobrecarga

Portanto, se t2 é do mesmo tipo que t1, ou t2 é um tipo de classe derivada de t1, T1 (t2) e T1 = t2 envolverão construtores, a resolução de sobrecarga será aplicada. Os construtores de cópia serão considerados durante a resolução de sobrecarga. Qual é selecionado depende das regras de resolução de sobrecarga.

É desnecessário dizer que, para todas as situações em negrito acima, como nova expressão (T * t1 = nova T (t2)), passagem de argumento, instrução de retorno, etc. etc., se t1 e t2 forem do mesmo tipo, copie os construtores será considerado para a resolução de sobrecarga.

Às vezes, mesmo o construtor de cópias se aplica, o compilador pode otimizá-lo, incluindo o famoso RVO (otimização do valor de retorno). Ou seja, se você disser retorno t2, diz a semântica, precisamos chamar o construtor copy, onde t2 é passado como argumento e um objeto temporário é construído usando t2.

No entanto, o compilador pode otimizar essa chamada do construtor de cópias, operando diretamente no objeto temporário, em vez de operar no local t2.

Todos os outros casos que envolvem = form, que não são de inicialização de cópia, serão considerados chamados de operador de atribuição de cópia.

4. Outras diferenças

O operador de atribuição de cópia pode ser virtual.

O exemplo a seguir é copiado do padrão.

struct B { operador int virtual = (int); operador B & virtual = (const B &);};estrutura D: B { operador int virtual = (int); D & operador virtual = (const B &);};D dobj1;D dobj2;B * bptr = & dobj1;void f () {bptr-> operator = (99); // chama D :: operator = (int)* bptr = 99; // idembptr-> operador = (dobj2); // chama D :: operator = (const B &)* bptr = dobj2; // idemdobj1 = dobj2; // chama D :: operator = declarado implicitamente = (const D &)}