Menu

Referências vs ponteiros em C++: quando usar cada um

Uma comparação prática entre referências e ponteiros em C++: o que têm em comum, onde diferem (reassociação, nulo, aritmética) e uma regra clara para saber qual escolher no dia a dia.

Esta página tem editores executáveis - edite, execute e veja a saída na hora.

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.

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 = other reassocie. 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: x morre quando f retorna e usar o resultado é comportamento indefinido. A mesma armadilha atinge os ponteiros (return &x;).
  • Forjar uma "referência nula". Escrever int& r = *p; quando p é nullptr é comportamento indefinido no instante em que você desreferencia, não uma referência "vazia" segura. Expresse a opcionalidade com um ponteiro ou std::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.

Coddy programming languages illustration

Aprenda a programar com o Coddy

COMEÇAR