Duas formas de se referir a algo
Você já conhece os ponteiros: variáveis que armazenam um endereço e permitem alcançar o objeto que vive ali. As referências são a outra ferramenta que o C++ oferece para trabalhar indiretamente com um objeto existente. Elas se sobrepõem o suficiente para que iniciantes muitas vezes não saibam qual escolher, então esta página as coloca lado a lado.
A versão curta: uma referência é um alias. Depois que int& r = x; é executado, r é x: o mesmo objeto, com outro nome. Um ponteiro é um objeto separado que por acaso guarda o endereço de outro. Essa única diferença determina todo o resto.
Uma referência é um alias
Uma referência precisa ser vinculada a um objeto no momento em que é criada e, a partir daí, todo uso da referência toca o original.
Repare que não há nenhum * para desreferenciar nem nenhum & para "pegar o endereço" no ponto de uso: você lê e escreve alias exatamente como um int comum. O & em int& alias faz parte do tipo, não é o operador de endereço.
Onde elas diferem
Os comportamentos abaixo são a própria razão de ambas as ferramentas existirem. Esta é a tabela para memorizar.
// reference pointer
// must be initialized? yes no (but should be)
// can be null? no yes (nullptr)
// can be reseated? no yes
// pointer arithmetic? no yes
// syntax to use it just the name *p or p->member
// taking address &ref == &original &p is the pointer's own address
Duas delas são as que mais confundem. Primeiro, uma referência nunca pode ser reassociada: atribuir algo a ela copia um valor para dentro do objeto referido, não aponta a referência para algo novo.
Um ponteiro, em contrapartida, é livre para apontar para outro lugar a qualquer momento:
Segundo, uma referência nunca pode ser legalmente nula, enquanto um ponteiro pode. Isso faz com que "nenhum valor" seja representável com um ponteiro, mas não com uma referência, uma propriedade na qual você vai se apoiar constantemente.
Escolhendo nos parâmetros de função
É aqui que a escolha mais aparece. Quando uma função precisa ler ou modificar o objeto de quem a chama, ambas funcionam, mas sinalizam intenções diferentes.
A versão com referência (addTax(cart)) é impossível de chamar com "nada", então dentro da função você nunca verifica se é nula: o objeto está garantido. A versão com ponteiro (applyDiscount(&cart)) anuncia no ponto de chamada, por meio do &, que o argumento pode mudar, e permite a quem chama passar nullptr para significar "não se aplica". Escolha aquela cuja garantia combine com a sua função.
Para parâmetros somente leitura de tipos grandes, a escolha idiomática é const T&: evita uma cópia e promete não modificar. Veja parâmetros de função para mais sobre passagem por valor versus passagem por referência.
Uma regra prática simples
Quando estiver em dúvida, prefira por padrão uma referência e só passe para um ponteiro quando precisar de uma capacidade que a referência não tem:
- Use uma referência quando o objeto sempre existir e sua identidade nunca mudar: o caso comum de parâmetros de função e aliases.
- Use um ponteiro quando qualquer uma destas condições for verdadeira:
- "Nada" é um estado válido (argumento opcional, uma busca que pode não encontrar correspondência): um ponteiro pode ser
nullptr. - Você precisa apontar para objetos diferentes ao longo do tempo: um ponteiro pode ser reassociado.
- Você está gerenciando memória da heap que vai liberar com
delete, ou percorrendo um array com aritmética de ponteiros.
- "Nada" é um estado válido (argumento opcional, uma busca que pode não encontrar correspondência): um ponteiro pode ser
Se nada disso se aplicar, uma referência é a escolha mais limpa e segura, porque o compilador garante por você "sempre válida, nunca reassociada".
Erros comuns a evitar
- Esperar que
ref = otherreassocie. Em vez disso, ele atribui um valor para dentro do objeto referido. Referências ficam vinculadas para a vida toda; se precisar reassociar, use um ponteiro. - Retornar uma referência (ou ponteiro) para uma variável local.
int& f() { int x = 5; return x; }retorna uma referência pendente:xmorre quandofretorna e usar o resultado é comportamento indefinido. A mesma armadilha atinge os ponteiros (return &x;). - Forjar uma "referência nula". Escrever
int& r = *p;quandopénullptré comportamento indefinido no instante em que você desreferencia, não uma referência "vazia" segura. Expresse a opcionalidade com um ponteiro oustd::optional. - Recorrer a um ponteiro por hábito. Se o argumento sempre existe e você nunca vai reassociá-lo, uma referência elimina toda uma categoria de verificações de nulo e de falhas. Não pague por capacidades que você não usa.
Próximo: Memória dinâmica
Até agora cada objeto que você referenciou ou apontou era criado automaticamente na pilha. A próxima página, memória dinâmica, cobre new e delete: pedir memória ao sistema operacional em tempo de execução, por que são os ponteiros (não as referências) que a possuem, e como esquecer de liberá-la causa vazamentos.
Perguntas frequentes
Qual é a diferença entre uma referência e um ponteiro em C++?
Uma referência é um alias para um objeto existente: precisa ser inicializada, nunca pode ser nula e nunca pode passar a se referir a outro objeto depois. Um ponteiro é uma variável separada que guarda um endereço: pode ser nulo, pode ser reassociado para apontar para outro lugar e suporta aritmética de ponteiros. Use a sintaxe & com referências e */-> com ponteiros.
Quando devo usar um ponteiro em vez de uma referência em C++?
Use um ponteiro quando "nada" for um estado válido (um argumento opcional, um resultado não encontrado), quando precisar reassociá-lo para apontar para objetos diferentes ao longo do tempo, ou quando você for dono de memória da heap que vai liberar com delete. Use uma referência quando o objeto sempre existir e nunca mudar de identidade, o que cobre a maioria dos parâmetros de função.
Uma referência pode ser nula em C++?
Não. Uma referência válida sempre se refere a um objeto real, então você nunca verifica se ela é nula. Se você criar uma referência a partir de um ponteiro nulo desreferenciado (int& r = *p; onde p é nulo), você obtém comportamento indefinido, não uma referência nula. Quando precisar expressar "talvez nada", use um ponteiro ou std::optional.