Pergunta

Desde um construtor de cópia

MyClass(const MyClass&);

e an = a sobrecarga do operador

MyClass& operator = (const MyClass&);

têm praticamente o mesmo código, o mesmo parâmetro, e diferem somente no retorno, é possível ter uma função comum para eles, tanto para usar?

Foi útil?

Solução

Sim. Existem duas opções comuns. Um - que geralmente é desencorajado - é chamar o operator= Do construtor de cópias explicitamente:

MyClass(const MyClass& other)
{
    operator=(other);
}

No entanto, fornecendo um bom operator= é um desafio quando se trata de lidar com o estado antigo e os problemas decorrentes da auto -atribuição. Além disso, todos os membros e bases recebem o padrão inicializados primeiro, mesmo que sejam atribuídos a de other. Isso pode nem ser válido para todos os membros e bases e, mesmo onde é válido, é semanticamente redundante e pode ser praticamente caro.

Uma solução cada vez mais popular é implementar operator= usando o construtor de cópias e um método de troca.

MyClass& operator=(const MyClass& other)
{
    MyClass tmp(other);
    swap(tmp);
    return *this;
}

ou até:

MyClass& operator=(MyClass other)
{
    swap(other);
    return *this;
}

UMA swap A função é normalmente simples de escrever, pois apenas troca a propriedade dos internos e não precisa limpar o estado existente ou alocar novos recursos.

As vantagens do idioma de cópia e troca é que ela é automaticamente auto -assinatura segura e - desde que a operação de swap não seja lançada - também é fortemente segura de exceção.

Para ser fortemente segura de exceção, um operador de atribuição por escrito de 'mão' normalmente precisa alocar uma cópia dos novos recursos antes de desalocar os recursos antigos do cessionário, para que, se ocorrer uma exceção alocando os novos recursos, o estado antigo ainda poderá ser devolvido para . Tudo isso vem de graça com cópia e troca, mas geralmente é mais complexo e, portanto, propenso a erros, para fazer do zero.

A única coisa a ter cuidado é garantir que o método de troca seja uma verdadeira troca, e não o padrão std::swap que usa o próprio construtor de cópias e o próprio operador de atribuição.

Normalmente um membro swap é usado. std::swap Funciona e é "não-laço" garantido com todos os tipos básicos e tipos de ponteiros. A maioria dos indicadores inteligentes também pode ser trocada com uma garantia de sem lances.

Outras dicas

O construtor de cópia executa primeiro tempo de inicialização de objetos que costumava ser matérias de memória.O operador de atribuição, OTOH, substitui os valores existentes com novos valores.Mais frequentemente do que nunca, isso envolve a demissão de recursos antigos (por exemplo, memória) e a atribuição de novos.

Se há uma semelhança entre os dois, é que o operador de atribuição executa a destruição e a cópia de construção.Alguns desenvolvedores usado para implementar de fato atribuição de local de destruição, seguida da colocação de cópia de construção.No entanto, este é um muito má idéia.(O que se isso é o operador de atribuição de uma classe base que chamou durante a atribuição de uma classe derivada?)

O que é geralmente considerado canônico de expressão de hoje é usando swap como Charles sugerido:

MyClass& operator=(MyClass other)
{
    swap(other);
    return *this;
}

Este utiliza a cópia de construção (note que other é copiado) e destruição (é destruída no final da função) -- e usa-los na ordem correta, também:construção (pode falhar) antes de destruição (não deve falhar).

Algo me incomoda:

MyClass& operator=(const MyClass& other)
{
    MyClass tmp(other);
    swap(tmp);
    return *this;
}

Primeiro, ler a palavra "troca" quando minha mente está pensando "cópia" irrita meu senso comum. Além disso, questiono o objetivo desse truque chique. Sim, quaisquer exceções na construção dos novos recursos (copiados) devem ocorrer antes da troca, o que parece uma maneira segura de garantir que todos os novos dados sejam preenchidos antes de fazê -los entrar em contato.

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. Tem um enorme efeito colateral: a cópia realmente aconteceu. Foi apenas uma limpeza de recursos que falhou. O estado do objeto de destino foi alterado, embora a operação pareça de fora para ter falhado.

Então, 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 existe a construção do objeto temporário, mas a próxima ação imediata é libertar todos os recursos atuais do destino antes de se mover (e anular para que não sejam frequentes duplos) os recursos da fonte.

Em vez de {construir, mover, destruir}, proponho {construir, destruir, mover}. A mudança, que é a ação mais perigosa, é a que dura depois depois que todo o resto foi resolvido.

Sim, a fracasso da destruição é um problema em qualquer esquema. Os dados são corrompidos (copiados quando você não achou) ou perdido (libertado quando você não achou que era). Perdido é melhor do que corrompido. Nenhum dado é melhor do que dados ruins.

Transferência em vez de trocar. Essa é minha sugestão de qualquer maneira.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top