Entendo a sintaxe e a semântica geral de ponteiros versus referências, mas como devo decidir quando é mais ou menos apropriado usar referências ou ponteiros em uma API?
Naturalmente, algumas situações precisam de uma ou de outra ( operator++
precisa de um argumento de referência), mas, em geral, estou descobrindo que prefiro usar ponteiros (e const), pois a sintaxe é clara: as variáveis estão sendo passadas destrutivamente.
Por exemplo, no seguinte código:
void add_one(int& n) { n += 1; }
void add_one(int* const n) { *n += 1; }
int main() {
int a = 0;
add_one(a); // Not clear that a may be modified
add_one(&a); // 'a' is clearly being passed destructively
}
Com o ponteiro, é sempre (mais) óbvio o que está acontecendo; portanto, para APIs e afins, onde a clareza é uma grande preocupação, os ponteiros não são mais apropriados que as referências? Isso significa que as referências devem ser usadas apenas quando necessário (por exemplo operator++
)? Há alguma preocupação de desempenho com um ou outro?
EDITAR (ATUALIZADO):
Além de permitir valores NULL e lidar com matrizes brutas, parece que a escolha se resume à preferência pessoal. Aceitei a resposta abaixo que faz referência ao Guia de estilo C ++ do Google , pois apresenta a visão de que "As referências podem ser confusas, pois possuem sintaxe de valor, mas semântica de ponteiro".
Devido ao trabalho adicional necessário para limpar argumentos de ponteiro que não devem ser NULL (por exemplo add_one(0)
, chamará a versão do ponteiro e interromperá durante o tempo de execução), faz sentido do ponto de vista da manutenção usar referências onde um objeto DEVE estar presente, embora seja uma vergonha perder a clareza sintática.
add_one(a);
que não está claro quea
será modificado? Diz exatamente o código: adicione um .addOneTo(...)
. Se não é isso que você quer fazer, basta olhar para a declaração.Respostas:
Use referência sempre que puder, indicadores sempre que precisar.
Evite ponteiros até não conseguir.
A razão é que os indicadores tornam as coisas mais difíceis de seguir / ler, menos seguras e muito mais perigosas do que qualquer outra construção.
Portanto, a regra geral é usar ponteiros apenas se não houver outra opção.
Por exemplo, retornar um ponteiro para um objeto é uma opção válida quando a função pode retornar nullptr em alguns casos e supõe-se que sim. Dito isto, uma opção melhor seria usar algo semelhante
boost::optional
.Outro exemplo é usar ponteiros para a memória não processada para manipulações de memória específicas. Isso deve estar oculto e localizado em partes muito estreitas do código, para ajudar a limitar as partes perigosas de toda a base de código.
No seu exemplo, não faz sentido usar um ponteiro como argumento porque:
nullptr
como argumento, estará em terreno de comportamento indefinido;Se o comportamento da função precisar trabalhar com ou sem um determinado objeto, o uso de um ponteiro como atributo sugere que você pode passar
nullptr
como argumento e isso é bom para a função. Esse é o tipo de contrato entre o usuário e a implementação.fonte
add_one(a)
não retornar o resultado, em vez de defini-lo por referência?add_one(a)
é confuso, é porque é nomeado incorretamente.add_one(&a)
teria a mesma confusão, só que agora você pode incrementar o ponteiro e não o objeto.add_one_inplace(a)
evitaria toda confusão.NULL
enullptr
, e os possui por um motivo. E não é um conselho bem-considerado ou realista que dê "nunca use ponteiros" e / ou "nunca use NULL, sempre useboost::optional
". Isso é loucura. Não me interpretem mal, os ponteiros brutos são necessários com menos frequência em C ++ do que em C, mas, ainda assim, são úteis, não são tão "perigosos" quanto algumas pessoas em C ++ gostam de afirmar (isso também é um exagero): quando é mais fácil usar apenas um ponteiro ereturn nullptr;
indicar um valor ausente ... Por que importar todo o Boost?if
é obsoleto e deve-se usarwhile() { break; }
, certo? Além disso, não se preocupe, eu já vi e trabalhei com grandes bases de código e, sim, se você é descuidado , a propriedade é um problema. Não, se você seguir as convenções, use-as de forma consistente, comente e documente seu código. Mas, afinal, eu deveria usar C porque sou burra demais para C ++, certo?As performances são exatamente as mesmas, pois as referências são implementadas internamente como ponteiros. Portanto, você não precisa se preocupar com isso.
Não existe uma convenção geralmente aceita sobre quando usar referências e ponteiros. Em alguns casos, você deve retornar ou aceitar referências (construtor de cópias, por exemplo), mas fora isso, você é livre para fazer o que quiser. Uma convenção bastante comum que encontrei é usar referências quando o parâmetro deve fazer referência a um objeto existente e ponteiros quando um valor NULL estiver ok.
Algumas convenções de codificação (como a do Google ) prescrevem que sempre se deve usar ponteiros ou referências const, porque as referências têm um pouco de sintaxe pouco clara: elas têm comportamento de referência, mas valorizam a sintaxe.
fonte
add_one
é quebrado :add_one(0); // passing a perfectly valid pointer value
, kaboom. Você precisa verificar se é nulo. Algumas pessoas responderão: "bem, apenas documentarei que minha função não funciona com nulo". Tudo bem, mas você derrota o objetivo da pergunta: se você for procurar na documentação para verificar se nulo está bom, também verá a declaração da função .int& i = *((int*)0);
Esta não é uma resposta válida. O problema no código anterior está no uso do ponteiro, não na referência . As referências nunca são nulas, ponto.Do C ++ FAQ Lite -
fonte
C with class
(que não é C ++).Minha regra de ouro é:
&
)const
se é um parâmetro de entrada)const T&
).int ¤t = someArray[i]
)Independentemente de qual você usar, não se esqueça de documentar suas funções e o significado de seus parâmetros se eles não forem óbvios.
fonte
Isenção de responsabilidade: além do fato de que as referências não podem ser NULL nem "rebote" (o que significa que elas não podem alterar o objeto do qual são o apelido), tudo se resume a uma questão de gosto, por isso não vou dizer "isto é melhor".
Dito isso, eu discordo de sua última declaração no post, pois não acho que o código perca clareza com referências. No seu exemplo,
pode ser mais claro que
pois você sabe que provavelmente o valor de a mudará. Por outro lado, porém, a assinatura da função
também não está claro: n será um único número inteiro ou uma matriz? Às vezes, você só tem acesso a cabeçalhos (mal documentados) e assinaturas como
não são fáceis de interpretar à primeira vista.
Portanto, as referências são tão boas quanto indicadores quando não é necessária (re) alocação ou religação (no sentido explicado anteriormente). Além disso, se um desenvolvedor usa apenas ponteiros para matrizes, as assinaturas de funções são um pouco menos ambíguas. Sem mencionar o fato de que a sintaxe dos operadores é muito mais legível com as referências.
fonte
Como outros já respondidos: Sempre use referências, a menos que a variável seja
NULL
/nullptr
seja realmente um estado válido.O ponto de vista de John Carmack sobre o assunto é semelhante:
http://www.altdevblogaday.com/2011/12/24/static-code-analysis/
Editar 2012-03-13
O usuário Bret Kuhns observa corretamente:
É verdade, mas a questão ainda permanece, mesmo ao substituir ponteiros brutos por ponteiros inteligentes.
Por exemplo, ambos
std::unique_ptr
estd::shared_ptr
podem ser construídos como ponteiros "vazios" por meio de seu construtor padrão:... significando que usá-los sem verificar se não estão vazios corre o risco de um acidente, que é exatamente o que trata a discussão de J. Carmack.
E então, temos o divertido problema de "como passamos um ponteiro inteligente como parâmetro de função?"
A resposta de Jon para a pergunta C ++ - passando referências para boost :: shared_ptr , e os comentários a seguir mostram que, mesmo assim, passar um ponteiro inteligente por cópia ou por referência não é tão claro quanto se gostaria (eu sou a favor de " por referência "por padrão, mas posso estar errado).
fonte
shared_ptr
, eunique_ptr
. A semântica de propriedade e as convenções de parâmetros in / out são atendidas por uma combinação dessas três partes e const'ness. Quase não há necessidade de ponteiros brutos em C ++, exceto quando se lida com código legado e algoritmos muito otimizados. As áreas em que são usadas devem ser o mais encapsuladas possível e converter qualquer ponteiro bruto no equivalente "moderno" semanticamente apropriado.Não é uma questão de gosto. Aqui estão algumas regras definitivas.
Se você quiser se referir a uma variável declarada estaticamente dentro do escopo em que foi declarada, use uma referência C ++ e ela será perfeitamente segura. O mesmo se aplica a um ponteiro inteligente declarado estaticamente. A passagem de parâmetros por referência é um exemplo desse uso.
Se você quiser se referir a algo de um escopo mais amplo que o escopo em que foi declarado, use um ponteiro inteligente contado como referência para que seja perfeitamente seguro.
Você pode se referir a um elemento de uma coleção com uma referência para conveniência sintática, mas não é seguro; o elemento pode ser excluído a qualquer momento.
Para manter com segurança uma referência a um elemento de uma coleção, você deve usar um ponteiro inteligente contado como referência.
fonte
Qualquer diferença de desempenho seria tão pequena que não justificaria usar uma abordagem menos clara.
Primeiro, um caso que não foi mencionado em que as referências geralmente são superiores são as
const
referências. Para tipos não simples, passar umconst reference
evita a criação de um temporário e não causa a confusão com a qual você se preocupa (porque o valor não é modificado). Aqui, forçar uma pessoa a passar um ponteiro causa a mesma confusão com a qual você está preocupado, pois ver o endereço recebido e passado para uma função pode fazer você pensar que o valor foi alterado.De qualquer forma, basicamente concordo com você. Não gosto de funções que façam referências para modificar seu valor quando não é muito óbvio que é isso que a função está fazendo. Eu também prefiro usar ponteiros nesse caso.
Quando você precisa retornar um valor em um tipo complexo, eu tendem a preferir referências. Por exemplo:
Aqui, o nome da função deixa claro que você está recebendo informações de volta em uma matriz. Portanto, não há confusão.
As principais vantagens das referências são que elas sempre contêm um valor válido, são mais limpas do que ponteiros e suportam polimorfismos sem precisar de nenhuma sintaxe extra. Se nenhuma dessas vantagens se aplicar, não há motivo para preferir uma referência a um ponteiro.
fonte
Copiado do wiki -
Concordo 100% com isso, e é por isso que acredito que você só deve usar uma referência quando tiver um bom motivo para fazê-lo.
fonte
Pontos a serem lembrados:
Ponteiros podem ser
NULL
, referências não podem serNULL
.As referências são mais fáceis de usar,
const
podem ser usadas para uma referência quando não queremos alterar valor e precisamos apenas de uma referência em uma função.Ponteiro usado com um
*
tempo referências usadas com um&
.Use ponteiros quando a operação aritmética do ponteiro for necessária.
Você pode ter ponteiros para um tipo de nulo,
int a=5; void *p = &a;
mas não pode ter uma referência a um tipo de nulo.Ponteiro versus referência
Veredicto quando usar o que
Ponteiro : Para matriz, lista de links, implementações em árvore e aritmética de ponteiros.
Referência : em parâmetros de função e tipos de retorno.
fonte
Há um problema com a regra " usar referências sempre que possível " e surge se você deseja manter a referência para uso posterior. Para ilustrar isso com um exemplo, imagine que você tenha as seguintes classes.
A princípio, pode parecer uma boa idéia ter um parâmetro no
RefPhone(const SimCard & card)
construtor passado por uma referência, porque impede a passagem de ponteiros errados / nulos para o construtor. De alguma forma, incentiva a alocação de variáveis na pilha e aproveita os benefícios do RAII.Mas então os temporários vêm para destruir seu mundo feliz.
Portanto, se você cegar cegamente às referências, reduz a possibilidade de passar ponteiros inválidos para a possibilidade de armazenar referências a objetos destruídos, que basicamente têm o mesmo efeito.
editar: observe que eu me apeguei à regra "Use referências sempre que puder, indicadores sempre que precisar. Evite indicadores até não conseguir". da resposta mais votada e aceita (outras respostas também sugerem isso). Embora deva ser óbvio, o exemplo não é mostrar que as referências são ruins. No entanto, eles podem ser mal utilizados, como ponteiros e podem trazer suas próprias ameaças ao código.
Existem as seguintes diferenças entre ponteiros e referências.
Levando isso em consideração, minhas regras atuais são as seguintes.
fonte
const SimCard & m_card;
é apenas um código mal escrito.const SimCard & m_card
está correto ou não, depende do contexto. A mensagem nesta postagem não é que as referências sejam inseguras (embora possam ser se alguém se esforçar). A mensagem é que você não deve ficar cego no mantra "usar referências sempre que possível". Exemplo é um resultado do uso agressivo da doutrina "use referências sempre que possível". Isso deve ficar claro.const SimCard & m_card
. Se você quiser ser eficiente com os temporários, adicione oexplicit RefPhone(const SimCard&& card)
construtor.A seguir estão algumas diretrizes.
Uma função usa dados passados sem modificá-los:
Se o objeto de dados for pequeno, como um tipo de dados interno ou uma estrutura pequena, transmita-o por valor.
Se o objeto de dados for uma matriz, use um ponteiro, porque essa é sua única opção. Faça do ponteiro um ponteiro para const.
Se o objeto de dados for uma estrutura de bom tamanho, use um ponteiro const ou uma referência const para aumentar a eficiência do programa. Você economiza tempo e espaço necessários para copiar uma estrutura ou um design de classe. Faça o ponteiro ou a referência const.
Se o objeto de dados é um objeto de classe, use uma referência const. A semântica do design de classe geralmente exige o uso de uma referência, que é o principal motivo pelo qual o C ++ adicionou esse recurso.
Uma função modifica os dados na função de chamada:
1.Se o objeto de dados for um tipo de dados interno, use um ponteiro. Se você localizar código como fixit (& x), em que x é um int, é bastante claro que essa função pretende modificar x.
2.Se o objeto de dados for uma matriz, use sua única opção: um ponteiro.
3.Se o objeto de dados for uma estrutura, use uma referência ou um ponteiro.
4.Se o objeto de dados for um objeto de classe, use uma referência.
Obviamente, essas são apenas diretrizes e pode haver razões para fazer escolhas diferentes. Por exemplo, cin usa referências para tipos básicos, para que você possa usar cin >> n em vez de cin >> & n.
fonte
As referências são mais limpas e fáceis de usar, além de ocultar melhor as informações. As referências não podem ser reatribuídas, no entanto. Se você precisar apontar primeiro para um objeto e depois para outro, use um ponteiro. As referências não podem ser nulas; portanto, se houver alguma chance de o objeto em questão ser nulo, você não deve usar uma referência. Você deve usar um ponteiro. Se você deseja lidar com a manipulação de objetos por conta própria, ou seja, se você deseja alocar espaço de memória para um objeto no Heap e não na Stack, use o Ponteiro
fonte
Seu exemplo escrito corretamente deve parecer
É por isso que as referências são preferíveis, se possível ...
fonte
Apenas colocando minha moeda. Acabei de realizar um teste. Um sorrateiro nisso. Eu apenas deixei o g ++ criar os arquivos de montagem do mesmo miniprograma usando ponteiros em comparação ao uso de referências. Ao olhar para a saída, eles são exatamente os mesmos. Diferente da simbologia. Portanto, analisando o desempenho (em um exemplo simples), não há problema.
Agora, no tópico ponteiros vs referências. IMHO Eu acho que a clareza está acima de tudo. Assim que leio o comportamento implícito, meus dedos começam a se enrolar. Concordo que é um bom comportamento implícito que uma referência não possa ser NULL.
Desreferenciar um ponteiro NULL não é o problema. ele travará seu aplicativo e será fácil depurar. Um problema maior são os ponteiros não inicializados que contêm valores inválidos. Isso provavelmente resultará em corrupção de memória, causando um comportamento indefinido sem uma origem clara.
É aqui que acho que as referências são muito mais seguras do que indicadores. E eu concordo com uma declaração anterior, de que a interface (que deve ser claramente documentada, veja o projeto por contrato, Bertrand Meyer) define o resultado dos parâmetros para uma função. Agora, levando tudo isso em consideração, minhas preferências vão para o uso de referências sempre que possível.
fonte
Para ponteiros, você precisa apontar para alguma coisa, para que os ponteiros custem espaço na memória.
Por exemplo, uma função que usa um ponteiro inteiro não aceita a variável inteira. Portanto, você precisará criar um ponteiro para que o primeiro passe para a função.
Quanto a uma referência, não custará memória. Você tem uma variável inteira e pode transmiti-la como uma variável de referência. É isso aí. Você não precisa criar uma variável de referência especialmente para ela.
fonte
&address
. Uma referência certamente custará memória se for membro de um objeto e, além disso, todos os compiladores existentes realmente implementam referências como endereços, para que você também não salve nada em termos de passagem de parâmetros ou desreferenciamento.