Desde um construtor de cópia
MyClass(const MyClass&);
e uma = sobrecarga de operador
MyClass& operator = (const MyClass&);
tem praticamente o mesmo código, o mesmo parâmetro, e só diferem no retorno, é possível ter uma função comum para os dois usarem?
c++
variable-assignment
copy-constructor
c++-faq
MPelletier
fonte
fonte
Respostas:
Sim. Existem duas opções comuns. Uma - que geralmente é desencorajada - é chamar
operator=
explicitamente o do construtor de cópia:MyClass(const MyClass& other) { operator=(other); }
No entanto, fornecer um bem
operator=
é um desafio quando se trata de lidar com o antigo estado e com as questões decorrentes da auto-atribuição. Além disso, todos os membros e bases são inicializados por padrão primeiro, mesmo se eles forem atribuídos a fromother
. Isso pode não ser válido para todos os membros e bases e, mesmo quando é válido, é semanticamente redundante e pode ser praticamente caro.Uma solução cada vez mais popular é implementar
operator=
usando o construtor de cópia e um método de troca.MyClass& operator=(const MyClass& other) { MyClass tmp(other); swap(tmp); return *this; }
ou mesmo:
MyClass& operator=(MyClass other) { swap(other); return *this; }
Uma
swap
função normalmente é simples de escrever, pois apenas troca a propriedade dos componentes internos e não precisa limpar o estado existente ou alocar novos recursos.As vantagens do idioma de cópia e troca é que ele é automaticamente seguro para auto-atribuição e - contanto que a operação de troca seja ilimitada - também é fortemente seguro para exceções.
Para ser totalmente seguro contra exceções, um operador de atribuição escrita 'à mão' normalmente tem que alocar uma cópia dos novos recursos antes de desalocar os recursos antigos do cessionário, de modo que se ocorrer uma exceção ao alocar os novos recursos, o estado antigo ainda pode ser devolvido para . Tudo isso vem de graça com o copy-and-swap, mas normalmente é mais complexo e, portanto, sujeito a erros para fazer do zero.
A única coisa a se ter cuidado é ter certeza de que o método swap é um swap verdadeiro, e não o padrão
std::swap
que usa o próprio construtor de cópia e operador de atribuição.Normalmente, um memberwise
swap
é usado.std::swap
funciona e é 'no-throw' garantido com todos os tipos básicos e tipos de ponteiro. A maioria dos ponteiros inteligentes também pode ser trocada com uma garantia de não lançamento.fonte
operator=
do ctor de cópia é na verdade muito ruim, porque primeiro inicializa todos os valores para algum padrão apenas para substituí-los pelos valores do outro objeto logo em seguida.assign
função de membro usada tanto pelo ctor de cópia quanto pelo operador de atribuição em alguns casos (para classes leves). Em outros casos (uso intensivo de recursos / casos de uso, identificador / corpo), uma cópia / troca é o caminho a seguir, é claro.O construtor de cópia executa a inicialização inicial de objetos que costumavam ser memória bruta. O operador de atribuição, OTOH, substitui os valores existentes por novos. Mais frequentemente do que nunca, isso envolve descartar recursos antigos (por exemplo, memória) e alocar novos.
Se houver uma semelhança entre os dois, é que o operador de atribuição executa a destruição e a construção da cópia. Alguns desenvolvedores costumavam realmente implementar a atribuição por destruição no local seguida por construção de cópia de posicionamento. No entanto, essa é uma ideia muito ruim. (E se este for o operador de atribuição de uma classe base que chamou durante a atribuição de uma classe derivada?)
O que normalmente é considerado o idioma canônico hoje em dia está usando
swap
como Charles sugeriu:MyClass& operator=(MyClass other) { swap(other); return *this; }
Este usa construção de cópia (note que
other
é copiado) e destruição (é destruído no final da função) - e também os usa na ordem certa: construção (pode falhar) antes da destruição (não deve falhar).fonte
swap
ser declaradovirtual
?Algo me incomoda sobre:
MyClass& operator=(const MyClass& other) { MyClass tmp(other); swap(tmp); return *this; }
Primeiro, ler a palavra “trocar” quando minha mente está pensando em “copiar” irrita meu bom senso. Além disso, questiono o objetivo desse truque sofisticado. Sim, quaisquer exceções na construção dos novos recursos (copiados) devem acontecer antes da troca, o que parece uma maneira segura de garantir que todos os novos dados sejam preenchidos antes de colocá-los no ar.
Isso é bom. Então, e as exceções que acontecem após a troca? (quando os recursos antigos são destruídos quando o objeto temporário sai do escopo) Da perspectiva do usuário da atribuição, a operação falhou, exceto que não. Isso tem um grande efeito colateral: a cópia realmente aconteceu. Foi apenas alguma limpeza de recursos que falhou. O estado do objeto de destino foi alterado, embora a operação pareça ter falhado do lado de fora.
Portanto, proponho em vez de "trocar" para fazer uma "transferência" mais natural:
MyClass& operator=(const MyClass& other) { MyClass tmp(other); transfer(tmp); return *this; }
Ainda há a construção do objeto temporário, mas a próxima ação imediata é liberar todos os recursos atuais do destino antes de mover (e anular para que não sejam liberados duas vezes) os recursos da origem para ele.
Em vez de {construir, mover, destruir}, proponho {construir, destruir, mover}. O movimento, que é a ação mais perigosa, é o último executado depois que todo o resto foi resolvido.
Sim, a falha de destruição é um problema em qualquer esquema. Os dados estão corrompidos (copiados quando você não pensava que eram) ou perdidos (liberados quando você não pensa que estão). Perdido é melhor do que corrompido. Nenhum dado é melhor do que dados ruins.
Transfira em vez de trocar. Essa é a minha sugestão de qualquer maneira.
fonte
First, reading the word "swap" when my mind is thinking "copy" irritates
-> Como um escritor de biblioteca, você geralmente conhece as práticas comuns (cópia + troca), e o ponto crucial émy mind
. Sua mente está realmente escondida atrás da interface pública. É disso que se trata o código reutilizável.