Devo preferir pointers ou referências em dados de membros?

Este é um exemplo simplificado para ilustrar a questão:

class A {}; class B { B(A& a) : a(a) {} A& a; }; class C { C() : b(a) {} A a; B b; }; 

Então, B é responsável por atualizar uma parte de C. Eu corri o código através de fiapos e isso se referia ao membro de referência: lint # 1725 . Isso fala sobre cuidar da cópia padrão e das atribuições, o que é justo, mas a cópia e a atribuição padrão também são ruins para os pointers, então há pouca vantagem nisso.

Eu sempre tento usar referências onde posso desde que os pointers nus introduzem a incerteza sobre quem é responsável por apagar esse ponteiro. Eu prefiro incorporar objects por valor, mas se eu precisar de um ponteiro, eu uso auto_ptr nos dados de membro da class que possui o ponteiro e transmito o object como uma referência.

Eu geralmente só usaria um ponteiro em dados de membro quando o ponteiro pudesse ser nulo ou poderia mudar. Existem outras razões para preferir pointers sobre referências para membros de dados?

É verdade que um object contendo uma referência não deve ser atribuível, uma vez que uma referência não deve ser alterada uma vez inicializada?

Evite membros de referência, porque eles restringem o que a implementação de uma class pode fazer (incluindo, como você mencionou, impedindo a implementação de um operador de atribuição) e não fornecem benefícios para o que a class pode fornecer.

Exemplos de problemas:

  • você é forçado a inicializar a referência na lista de inicializadores de cada construtor: não há como fatorar essa boot em outra function ( até C ++ 0x, de qualquer forma, editar: C ++ agora tem construtores de delegação )
  • a referência não pode ser rebatida ou ser nula. Isso pode ser uma vantagem, mas se o código precisar ser alterado para permitir a rebinding ou para o membro ser nulo, todos os usos do membro precisarão ser alterados
  • Ao contrário dos membros do ponteiro, as referências não podem ser facilmente substituídas por pointers inteligentes ou iteradores, pois a refatoração pode exigir
  • Sempre que uma referência é usada, ela se parece com o tipo de valor (operador .etc), mas se comporta como um ponteiro (pode ser pendurado) – por exemplo, o Google Style Guide o desencoraja

Minha regra de ouro:

  • Use um membro de referência quando quiser que a vida do seu object dependa da vida de outros objects : é uma maneira explícita de dizer que você não permite que o object fique vivo sem uma instância válida de outra class – por causa de não atribuição e a obrigação de obter a boot das referências através do construtor. É uma boa maneira de projetar sua class sem assumir nada sobre sua instância ser membro ou não de outra class. Você só assume que suas vidas estão diretamente ligadas a outras instâncias. Ele permite que você mude mais tarde como você usa sua instância de class (com novo, como uma instância local, como um membro da class, gerado por um pool de memory em um gerenciador, etc.)
  • Use o ponteiro em outros casos : Quando você quiser que o membro seja alterado posteriormente, use um ponteiro ou um ponteiro const para certificar-se de ler apenas a instância apontada. Se esse tipo é supostamente copiável, você não pode usar referências de qualquer maneira. Às vezes você também precisa inicializar o membro depois de uma chamada de function especial (init () por exemplo) e então você simplesmente não tem escolha a não ser usar um ponteiro. MAS: use afirma em toda a sua function de membro para detectar rapidamente o estado incorreto do ponteiro!
  • Nos casos em que você deseja que o tempo de vida do object dependa do tempo de vida de um object externo, também é necessário que esse tipo seja copiável, use membros de ponteiro, mas argumento de referência no construtor Dessa forma, você indica que o tempo de vida desse object depende no tempo de vida do argumento MAS a implementação usa pointers para ainda serem copiáveis. Contanto que esses membros sejam alterados apenas por cópia e seu tipo não tenha um construtor padrão, o tipo deve cumprir os dois objectives.

Objetos raramente devem permitir atribuir e outras coisas como comparação. Se você considerar algum modelo de negócios com objects como ‘Departamento’, ‘Funcionário’, ‘Diretor’, é difícil imaginar um caso em que um funcionário será atribuído a outro.

Portanto, para objects de negócios, é muito bom descrever relacionamentos um-para-um e um-para-muitos como referências e não como pointers.

E provavelmente está tudo bem descrever um relacionamento de um ou zero como um ponteiro.

Então, ‘não podemos atribuir’ então fator.
Muitos programadores apenas se acostumam com pointers e é por isso que eles encontrarão algum argumento para evitar o uso de referência.

Ter um ponteiro como membro forçará você ou membro de sua equipe a verificar o ponteiro novamente e novamente antes de usar, com o comentário “apenas no caso”. Se um ponteiro pode ser zero, então o ponteiro provavelmente é usado como um tipo de flag, o que é ruim, já que todo object tem que desempenhar seu próprio papel.

Use referências quando puder, e pointers quando você precisar.

Em alguns casos importantes, a designação simplesmente não é necessária. Geralmente, esses são empacotadores de algoritmos leves que facilitam o cálculo sem sair do escopo. Esses objects são os principais candidatos para membros de referência, pois você pode ter certeza de que eles sempre mantêm uma referência válida e nunca precisam ser copiados.

Nesses casos, certifique-se de tornar o operador de atribuição (e muitas vezes também o construtor de cópia) não utilizável (herdando boost::noncopyable ou declarando-o como private).

No entanto, como os usuários já comentaram, o mesmo não é verdadeiro para a maioria dos outros objects. Aqui, usar membros de referência pode ser um problema enorme e geralmente deve ser evitado.

Como todos parecem estar distribuindo regras gerais, eu ofereço dois:

  • Nunca, nunca use referências de uso como membros da class. Eu nunca fiz isso em meu próprio código (exceto para provar a mim mesmo que eu estava certo nesta regra) e não posso imaginar um caso em que eu faria isso. A semântica é muito confusa, e não é para isso que as referências foram projetadas.

  • Sempre, sempre, use referências ao passar parâmetros para funções, exceto para os tipos básicos, ou quando o algoritmo requer uma cópia.

Essas regras são simples e me mantiveram em bom lugar. Deixo de fazer regras sobre o uso de pointers inteligentes (mas, por favor, não auto_ptr) como membros da class para os outros.

Sim para: É verdade que um object contendo uma referência não deve ser atribuível, uma vez que uma referência não deve ser alterada depois de inicializada?

Minhas regras gerais para membros de dados:

  • nunca use uma referência, porque impede a atribuição
  • se sua class é responsável pela exclusão, use scoped_ptr do boost (que é mais seguro que um auto_ptr)
  • caso contrário, use um ponteiro ou ponteiro const

Eu geralmente só usaria um ponteiro em dados de membro quando o ponteiro pudesse ser nulo ou poderia mudar. Existem outras razões para preferir pointers sobre referências para membros de dados?

Sim. Legibilidade do seu código. Um ponteiro torna mais óbvio que o membro é uma referência (ironicamente :)), e não um object contido, porque quando você o usa, você precisa excluí-lo. Eu sei que algumas pessoas pensam que é antiquado, mas eu ainda acho que isso simplesmente evita confusão e erros.

Eu aconselho contra membros de dados de referência, porque você nunca sabe quem vai derivar de sua class e o que eles podem querer fazer. Eles podem não querer fazer uso do object referenciado, mas, sendo uma referência, você os forçou a fornecer um object válido. Eu fiz isso para mim mesmo o suficiente para parar de usar membros de dados de referência.