Eu sei que o compilador C ++ cria um construtor de cópia para uma classe. Nesse caso, temos que escrever um construtor de cópia definido pelo usuário? Voce pode dar alguns exemplos?
c++
copy-constructor
penguru
fonte
fonte
Respostas:
O construtor de cópia gerado pelo compilador faz a cópia por membro. Às vezes, isso não é suficiente. Por exemplo:
class Class { public: Class( const char* str ); ~Class(); private: char* stored; }; Class::Class( const char* str ) { stored = new char[srtlen( str ) + 1 ]; strcpy( stored, str ); } Class::~Class() { delete[] stored; }
neste caso, a cópia do
stored
membro não duplicará o buffer (apenas o ponteiro será copiado), então a primeira cópia a ser destruída que compartilha o buffer será chamada comdelete[]
êxito e a segunda terá um comportamento indefinido. Você precisa do construtor de cópia profunda (e também do operador de atribuição).Class::Class( const Class& another ) { stored = new char[strlen(another.stored) + 1]; strcpy( stored, another.stored ); } void Class::operator = ( const Class& another ) { char* temp = new char[strlen(another.stored) + 1]; strcpy( temp, another.stored); delete[] stored; stored = temp; }
fonte
delete stored[];
e eu acredito que deveria serdelete [] stored;
std::string
. A ideia geral é que apenas as classes de utilitários que gerenciam recursos precisam sobrecarregar os Três Grandes e que todas as outras classes devem apenas usar essas classes de utilitários, eliminando a necessidade de definir qualquer um dos Três Grandes.Estou um pouco irritado porque a regra do
Rule of Five
não foi citada.Esta regra é muito simples:
Mas há uma orientação mais geral que você deve seguir, que deriva da necessidade de escrever um código seguro para exceção:
Aqui
@sharptooth
's código ainda é (principalmente) bem, no entanto, se ele fosse para adicionar um segundo atributo à sua classe que não seria. Considere a seguinte classe:class Erroneous { public: Erroneous(); // ... others private: Foo* mFoo; Bar* mBar; }; Erroneous::Erroneous(): mFoo(new Foo()), mBar(new Bar()) {}
O que acontece se
new Bar
jogar? Como você exclui o objeto apontado pormFoo
? Existem soluções (nível de função try / catch ...), eles simplesmente não escalam.A maneira adequada de lidar com a situação é usar classes adequadas em vez de ponteiros brutos.
class Righteous { public: private: std::unique_ptr<Foo> mFoo; std::unique_ptr<Bar> mBar; };
Com a mesma implementação de construtor (ou, na verdade, usando
make_unique
), agora tenho segurança de exceção de graça !!! Não é emocionante? E o melhor de tudo, não preciso mais me preocupar com um destruidor adequado! Eu preciso escrever meu próprioCopy Constructor
eAssignment Operator
porqueunique_ptr
não define essas operações ... mas não importa aqui;)E, portanto,
sharptooth
a aula de revisitada:class Class { public: Class(char const* str): mData(str) {} private: std::string mData; };
Eu não sei sobre você, mas acho o meu mais fácil;)
fonte
Posso me lembrar de minha prática e pensar nos seguintes casos, quando é preciso lidar com a declaração / definição explícita do construtor de cópia. Eu agrupei os casos em duas categorias
Correção / Semântica
Coloco nesta seção os casos em que declarar / definir o construtor de cópia é necessário para a operação correta dos programas que usam aquele tipo.
Depois de ler esta seção, você aprenderá sobre as várias armadilhas de permitir que o compilador gere o construtor de cópia por conta própria. Portanto, como Seand observou em sua resposta , é sempre seguro desativar a capacidade de cópia para uma nova classe e ativá-la deliberadamente mais tarde, quando realmente necessário.
Como tornar uma classe não copiável em C ++ 03
Declare um construtor de cópia privado e não forneça uma implementação para ele (de modo que a construção falhe no estágio de vinculação, mesmo se os objetos daquele tipo forem copiados no próprio escopo da classe ou por seus amigos).
Como tornar uma classe não copiável em C ++ 11 ou mais recente
Declare o construtor de cópia com
=delete
no final.Cópia superficial vs profunda
Este é o caso mais bem compreendido e, na verdade, o único mencionado nas outras respostas. shaprtooth tem coberto muito bem. Só quero acrescentar que os recursos de cópia profunda que deveriam pertencer exclusivamente ao objeto podem se aplicar a qualquer tipo de recurso, dos quais a memória alocada dinamicamente é apenas um tipo. Se necessário, copiar profundamente um objeto também pode exigir
Objetos de autorregistro
Considere uma classe onde todos os objetos - não importa como foram construídos - DEVEM ser registrados de alguma forma. Alguns exemplos:
O exemplo mais simples: manter a contagem total de objetos existentes atualmente. O registro de objetos trata apenas de incrementar o contador estático.
Um exemplo mais complexo é ter um registro singleton, onde as referências a todos os objetos existentes desse tipo são armazenadas (para que as notificações possam ser entregues a todos eles).
Os ponteiros inteligentes contados por referência podem ser considerados apenas um caso especial nesta categoria: o novo ponteiro "registra-se" com o recurso compartilhado em vez de em um registro global.
Essa operação de autorregistro deve ser executada por QUALQUER construtor do tipo e o construtor de cópia não é exceção.
Objetos com referências cruzadas internas
Alguns objetos podem ter uma estrutura interna não trivial com referências cruzadas diretas entre seus diferentes subobjetos (na verdade, apenas uma dessas referências cruzadas internas é suficiente para acionar este caso). O construtor de cópia fornecido pelo compilador quebrará as associações internas entre objetos , convertendo- as em associações entre objetos .
Um exemplo:
struct MarriedMan; struct MarriedWoman; struct MarriedMan { // ... MarriedWoman* wife; // association }; struct MarriedWoman { // ... MarriedMan* husband; // association }; struct MarriedCouple { MarriedWoman wife; // aggregation MarriedMan husband; // aggregation MarriedCouple() { wife.husband = &husband; husband.wife = &wife; } }; MarriedCouple couple1; // couple1.wife and couple1.husband are spouses MarriedCouple couple2(couple1); // Are couple2.wife and couple2.husband indeed spouses? // Why does couple2.wife say that she is married to couple1.husband? // Why does couple2.husband say that he is married to couple1.wife?
Apenas objetos que atendam a certos critérios podem ser copiados
Pode haver classes em que os objetos são seguros para copiar enquanto estão em algum estado (por exemplo, estado padrão construído) e não seguros para copiar de outra forma. Se quisermos permitir a cópia segura de objetos de cópia, então - se estiver programando defensivamente - precisamos de uma verificação em tempo de execução no construtor de cópia definido pelo usuário.
Subobjetos não copiáveis
Às vezes, uma classe que deveria ser copiável agrega subobjetos não copiáveis. Normalmente, isso acontece para objetos com estado não observável (esse caso é discutido em mais detalhes na seção "Otimização" abaixo). O compilador apenas ajuda a reconhecer esse caso.
Subobjetos quase copiáveis
Uma classe, que deve ser copiável, pode agregar um subobjeto de um tipo quase copiável. Um tipo quase copiável não fornece um construtor de cópia no sentido estrito, mas possui outro construtor que permite criar uma cópia conceitual do objeto. A razão para tornar um tipo quase copiável é quando não há acordo total sobre a semântica de cópia do tipo.
Em seguida, a decisão final é deixada para os usuários desse tipo - ao copiar objetos, eles devem especificar explicitamente (por meio de argumentos adicionais) o método de cópia pretendido.
No caso de uma abordagem não defensiva da programação, também é possível que um construtor de cópia regular e um construtor de quase cópia estejam presentes. Isso pode ser justificado quando, na grande maioria dos casos, um único método de cópia deve ser aplicado, enquanto em situações raras, mas bem conhecidas, métodos de cópia alternativos devem ser usados. Então, o compilador não reclamará que é incapaz de definir implicitamente o construtor de cópia; será de responsabilidade exclusiva dos usuários lembrar e verificar se um subobjeto daquele tipo deve ser copiado por meio de um construtor de quase cópia.
Não copie o estado que está fortemente associado à identidade do objeto
Em casos raros, um subconjunto de objetos observáveis estado pode constituir (ou ser considerado) uma parte inseparável da identidade do objeto e não deve ser transferível para outros objetos (embora isso possa ser um tanto controverso).
Exemplos:
O UID do objeto (mas este também pertence ao caso de "autorregistro" de cima, já que o id deve ser obtido em um ato de autorregistro).
Histórico do objeto (por exemplo, a pilha Desfazer / Refazer) no caso em que o novo objeto não deve herdar o histórico do objeto de origem, mas em vez disso, começar com um único item de histórico " Copiado às <TIME> de <OTHER_OBJECT_ID> ".
Nesses casos, o construtor de cópia deve ignorar a cópia dos subobjetos correspondentes.
Obrigando a assinatura correta do construtor de cópia
A assinatura do construtor de cópia fornecido pelo compilador depende de quais construtores de cópia estão disponíveis para os subobjetos. Se pelo menos um subobjeto não tiver um construtor de cópia real (tomando o objeto de origem por referência constante), mas em vez disso tiver um construtor de cópia mutante (tomando o objeto de origem por referência não constante), o compilador não terá escolha mas para declarar implicitamente e então definir um construtor de cópia mutante.
Agora, o que aconteceria se o construtor de cópia "mutante" do tipo do subobjeto não alterasse realmente o objeto de origem (e fosse simplesmente escrito por um programador que não sabe sobre a
const
palavra - chave)? Se não podemos ter esse código corrigido adicionando o que faltaconst
, então a outra opção é declarar nosso próprio construtor de cópia definido pelo usuário com uma assinatura correta e cometer o pecado de mudar para umconst_cast
.Cópia na gravação (COW)
Um contêiner COW que forneceu referências diretas aos seus dados internos DEVE ser copiado em profundidade no momento da construção, caso contrário, ele pode se comportar como um indicador de contagem de referência.
Otimização
Nos casos a seguir, você pode querer / precisar definir seu próprio construtor de cópia por questões de otimização:
Otimização da estrutura durante a cópia
Considere um contêiner que suporte operações de remoção de elemento, mas pode fazer isso simplesmente marcando o elemento removido como excluído e reciclar seu slot mais tarde. Quando uma cópia desse contêiner é feita, pode fazer sentido compactar os dados remanescentes em vez de preservar os slots "excluídos" como estão.
Pular a cópia do estado não observável
Um objeto pode conter dados que não fazem parte de seu estado observável. Normalmente, são dados armazenados em cache / memoized acumulados durante a vida útil do objeto, a fim de acelerar certas operações lentas de consulta realizadas pelo objeto. É seguro pular a cópia desses dados, pois eles serão recalculados quando (e se!) As operações relevantes forem realizadas. Copiar esses dados pode ser injustificado, pois pode ser rapidamente invalidado se o estado observável do objeto (a partir do qual os dados armazenados em cache são derivados) for modificado por operações de mutação (e se não vamos modificar o objeto, por que estamos criando um profundo copia então?)
Essa otimização é justificada apenas se os dados auxiliares forem grandes em comparação com os dados que representam o estado observável.
Desativar cópia implícita
C ++ permite desabilitar a cópia implícita, declarando o construtor de cópia
explicit
. Então, os objetos daquela classe não podem ser passados para funções e / ou retornados de funções por valor. Esse truque pode ser usado para um tipo que parece ser leve, mas é realmente muito caro para copiar (embora, torná-lo quase copiável pode ser uma escolha melhor).TODOs
fonte
Se você tiver uma classe com conteúdo alocado dinamicamente. Por exemplo, você armazena o título de um livro como um caractere * e define o título com novo, a cópia não funcionará.
Você teria que escrever um construtor de cópia que faça
title = new char[length+1]
e entãostrcpy(title, titleIn)
. O construtor de cópia faria apenas uma cópia "superficial".fonte
O Construtor de cópia é chamado quando um objeto é passado por valor, retornado por valor ou copiado explicitamente. Se não houver um construtor de cópia, c ++ cria um construtor de cópia padrão que faz uma cópia superficial. Se o objeto não tiver ponteiros para memória alocada dinamicamente, uma cópia superficial servirá.
fonte
Geralmente é uma boa ideia desativar o copy ctor e o operator =, a menos que a classe precise especificamente deles. Isso pode evitar ineficiências, como passar um argumento por valor quando a referência é pretendida. Além disso, os métodos gerados pelo compilador podem ser inválidos.
fonte
Vamos considerar o snippet de código abaixo:
class base{ int a, *p; public: base(){ p = new int; } void SetData(int, int); void ShowData(); base(const base& old_ref){ //No coding present. } }; void base :: ShowData(){ cout<<this->a<<" "<<*(this->p)<<endl; } void base :: SetData(int a, int b){ this->a = a; *(this->p) = b; } int main(void) { base b1; b1.SetData(2, 3); b1.ShowData(); base b2 = b1; //!! Copy constructor called. b2.ShowData(); return 0; }
Output: 2 3 //b1.ShowData(); 1996774332 1205913761 //b2.ShowData();
b2.ShowData();
fornece saída de lixo porque há um construtor de cópia definido pelo usuário criado sem nenhum código escrito para copiar dados explicitamente. Portanto, o compilador não cria o mesmo.Pensei apenas em compartilhar esse conhecimento com todos vocês, embora a maioria de vocês já saiba disso.
Saúde ... Boa codificação !!!
fonte