Ponteiro vs. Referência

256

Qual seria a melhor prática ao atribuir a uma função a variável original para trabalhar:

unsigned long x = 4;

void func1(unsigned long& val) {
     val = 5;            
}
func1(x);

ou:

void func2(unsigned long* val) {
     *val = 5;
}
func2(&x);

IOW: Existe algum motivo para escolher um sobre o outro?

Jack Reza
fonte
1
As referências são obviamente valiosas, mas eu venho de C, onde os ponteiros estão por toda parte. É preciso ter conhecimentos de ponteiros primeiro para entender o valor das referências.
Jay D
Como isso se encaixa em um objetivo, como a transparência referencial da programação funcional? E se você sempre quiser que as funções retornem novos objetos e nunca alterem internamente o estado, especialmente não as variáveis ​​passadas para a função. Existe alguma maneira de esse conceito ainda ser usado com ponteiros e referências em uma linguagem como C ++. (Note, eu estou supondo que alguém já tem o objetivo de transparência referencial Eu não estou interessado em falar sobre se é ou não é uma boa meta de ter..)
Ely
Prefira referências. Ponteiros do usuário quando você não tem escolha.
Ferruccio

Respostas:

285

Minha regra de ouro é:

Use ponteiros se desejar fazer a aritmética do ponteiro com eles (por exemplo, incrementar o endereço do ponteiro para percorrer uma matriz) ou se precisar passar um ponteiro NULL.

Use referências caso contrário.

Nils Pipenbrinck
fonte
10
Excelente ponto em relação a um ponteiro ser NULL. Se você tiver um parâmetro de ponteiro, verifique explicitamente se não é NULL ou pesquise todos os usos da função para garantir que nunca seja NULL. Este esforço não é necessário para referências.
Richard Corden
26
Explique o que você quer dizer com aritmética. Um novo usuário pode não entender que você deseja ajustar o que o ponteiro está apontando.
Martin York
7
Martin, por aritmética, quero dizer que você passa um ponteiro para uma estrutura, mas sabe que não é uma estrutura simples, mas uma matriz dela. Nesse caso, você pode indexá-lo usando [] ou fazer aritmética usando ++ / - no ponteiro. Essa é a diferença em poucas palavras.
Nils Pipenbrinck
2
Martin, você só pode fazer isso com ponteiros diretamente. Não com referências. Claro que você pode apontar para uma referência e fazer a mesma coisa na prática, mas se fizer isso, você termina com um código muito sujo.
Nils Pipenbrinck
1
E o polimorfismo (por exemplo Base* b = new Derived())? Parece um caso que não pode ser tratado sem ponteiros.
Chris Redford
72

Realmente acho que você se beneficiará do estabelecimento da seguinte função, chamando diretrizes de codificação:

  1. Como em todos os outros lugares, sempre esteja constcorreto.

    • Nota: Isso significa, entre outras coisas, que apenas os valores externos (consulte o item 3) e os valores passados ​​pelo valor (consulte o item 4) podem não ter o constespecificador.
  2. Somente passe um valor por ponteiro se o valor 0 / NULL for uma entrada válida no contexto atual.

    • Justificativa 1: Como chamador , você vê que tudo o que você passa deve estar em um estado utilizável.

    • Fundamentação 2: Conforme chamado , você sabe que tudo o que entra está em um estado utilizável. Portanto, nenhuma verificação NULL ou manipulação de erros precisa ser feita para esse valor.

    • Razão 3: As razões 1 e 2 serão aplicadas pelo compilador . Sempre pegue erros em tempo de compilação, se puder.

  3. Se um argumento de função for um valor externo, passe-o por referência.

    • Fundamentação da petição: Não queremos quebrar o item 2 ...
  4. Escolha "passar por valor" sobre "passar por referência de const" apenas se o valor for um POD (estrutura de dados antiga simples ) ou pequeno o suficiente (em memória) ou de outras maneiras barato o suficiente (em tempo) para copiar.

    • Justificativa: Evite cópias desnecessárias.
    • Nota: pequeno o suficiente e barato o suficiente não são mensuráveis ​​absolutos.
Johann Gerell
fonte
Falta a diretriz quando: ... "quando usar const &" ... A diretriz 2 deve ser escrita "para valores [in], somente passe pelo ponteiro se NULL for válido. Caso contrário, use const const (ou para" pequenos" objetos, cópia), ou referência se ele é um valor [out] Eu estou monitorando este post para potencialmente adicionar um +1..
paercebal
O item 1 cobre o caso que você descreve.
Johann Gerell 22/09/08
É um pouco difícil passar um parâmetro out por referência, se não for construtível por padrão. Isso é bastante comum no meu código - toda a razão para uma função criar esse objeto externo é porque não é trivial.
MSalters 22/09/08
@ MSalters: Se você vai alocar a memória dentro da função (que eu acho que é isso que você quer dizer), por que não retornar um ponteiro para a memória alocada?
Kleist
@Kleist: Em nome do @MSalters, existem muitos motivos possíveis. Uma é que você já pode ter alocado memória para preencher, como um tamanho pré-definido std::vector<>.
Johann Gerell
24

Isso acaba sendo subjetivo. A discussão até agora é útil, mas não acho que haja uma resposta correta ou decisiva para isso. Muito dependerá das diretrizes de estilo e das suas necessidades no momento.

Embora existam alguns recursos diferentes (se algo pode ou não ser NULL) com um ponteiro, a maior diferença prática para um parâmetro de saída é puramente a sintaxe. O Guia de estilo C ++ do Google ( https://google.github.io/styleguide/cppguide.html#Reference_Arguments ), por exemplo, exige apenas ponteiros para os parâmetros de saída e permite apenas referências que são const. O raciocínio é de legibilidade: algo com sintaxe de valor não deve ter um significado semântico de ponteiro. Não estou sugerindo que isso seja necessariamente certo ou errado, mas acho que o ponto aqui é que é uma questão de estilo, não de correção.

Aaron N. Tubbs
fonte
O que significa que as referências têm sintaxe de valor, mas significado semântico de ponteiro?
Eric Andrew Lewis
Parece que você está passando uma cópia, pois a parte "passar por referência" é aparente apenas na definição da função (sintaxe do valor), mas você não está copiando o valor que passa, essencialmente passa um ponteiro sob o capô, o que permite a função para modificar seu valor.
Phant0m
Não se deve esquecer que o guia de estilo do Google C ++ é muito detestado.
Deduplicator
7

Você deve passar um ponteiro se quiser modificar o valor da variável. Mesmo que tecnicamente passar uma referência ou um ponteiro seja o mesmo, passar um ponteiro no seu caso de uso é mais legível, pois "anuncia" o fato de que o valor será alterado pela função.

Max Caceres
fonte
2
Se você seguir as diretrizes de Johann Gerell, uma referência não const também anuncia uma variável mutável, portanto, o ponteiro não tem essa vantagem aqui.
Alexander Kondratskiy
4
@AlexanderKondratskiy: você está faltando o ponto ... você não pode ver instantaneamente no site da chamada se a função de chamada aceita um paramter como constou não constde referência, mas você pode ver se o parâmetro do passado ala &xvs. xe uso essa convenção para codificar se o parâmetro pode ser modificado. (Dito isso, há momentos em que você deseja passar por um constponteiro, então a convenção é apenas uma dica. É possível suspeitar que algo pode ser modificado quando não será, é menos perigoso do que pensar que não será quando será. ....)
Tony Delroy
5

Se você possui um parâmetro em que pode precisar indicar a ausência de um valor, é prática comum transformar o parâmetro em um valor de ponteiro e passar NULL.

Uma solução melhor na maioria dos casos (de uma perspectiva de segurança) é usar o boost :: opcional . Isso permite que você transmita valores opcionais por referência e também como um valor de retorno.

// Sample method using optional as input parameter
void PrintOptional(const boost::optional<std::string>& optional_str)
{
    if (optional_str)
    {
       cout << *optional_str << std::endl;
    }
    else
    {
       cout << "(no string)" << std::endl;
    }
}

// Sample method using optional as return value
boost::optional<int> ReturnOptional(bool return_nothing)
{
    if (return_nothing)
    {
       return boost::optional<int>();
    }

    return boost::optional<int>(42);
}
Kiley Hykawy
fonte
4

Ponteiros

  • Um ponteiro é uma variável que contém um endereço de memória.
  • Uma declaração de ponteiro consiste em um tipo base, um * e o nome da variável.
  • Um ponteiro pode apontar para qualquer número de variáveis ​​durante a vida útil
  • Um ponteiro que atualmente não aponta para um local válido da memória recebe o valor nulo (que é zero)

    BaseType* ptrBaseType;
    BaseType objBaseType;
    ptrBaseType = &objBaseType;
  • O & é um operador unário que retorna o endereço de memória de seu operando.

  • O operador de desreferenciação (*) é usado para acessar o valor armazenado na variável para a qual o ponteiro aponta.

       int nVar = 7;
       int* ptrVar = &nVar;
       int nVar2 = *ptrVar;

Referência

  • Uma referência (&) é como um alias para uma variável existente.

  • Uma referência (&) é como um ponteiro constante que é automaticamente desreferenciado.

  • Geralmente é usado para listas de argumentos de função e valores de retorno de função.

  • Uma referência deve ser inicializada quando é criada.

  • Depois que uma referência é inicializada para um objeto, ela não pode ser alterada para se referir a outro objeto.

  • Você não pode ter referências NULL.

  • Uma referência const pode se referir a uma const int. Isso é feito com uma variável temporária com o valor da const

    int i = 3;    //integer declaration
    int * pi = &i;    //pi points to the integer i
    int& ri = i;    //ri is refers to integer i – creation of reference and initialization

insira a descrição da imagem aqui

insira a descrição da imagem aqui

Saurabh Raoot
fonte
3

Uma referência é um ponteiro implícito. Basicamente, você pode alterar o valor para o qual a referência aponta, mas não pode alterar a referência para apontar para outra coisa. Então, meus 2 centavos é que, se você quiser alterar apenas o valor de um parâmetro, passe-o como referência, mas se precisar alterar o parâmetro para apontar para um objeto diferente, passe-o usando um ponteiro.


fonte
3

Considere a palavra-chave de saída do C #. O compilador requer que o chamador de um método aplique a palavra-chave out a qualquer argumento de saída, mesmo que ele já saiba se o é. Isso tem como objetivo melhorar a legibilidade. Embora com os IDEs modernos, estou inclinado a pensar que este é um trabalho de destaque de sintaxe (ou semântica).

Daniel Earwicker
fonte
erro de digitação: semântico, não simétrico; +1 Concordo com possibilidade de destacar, em vez de escrever (C #) ou & (no caso de C, há referências)
peenut
2

Passe por referência const, a menos que haja uma razão pela qual você queira alterar / manter o conteúdo que está passando.

Este será o método mais eficiente na maioria dos casos.

Certifique-se de usar const em cada parâmetro que você não deseja alterar, pois isso não apenas protege você de fazer algo estúpido na função, mas também fornece aos outros usuários o que a função faz com os valores passados. Isso inclui fazer um ponteiro const quando você deseja alterar apenas o que é indicado ...

NotJarvis
fonte
2

Ponteiros:

  • Pode ser atribuído nullptr(ou NULL).
  • No site de chamada, você deve usar &se o seu tipo não for um ponteiro, tornando explicitamente a modificação do seu objeto.
  • Os ponteiros podem ser recuperados.

Referências:

  • Não pode ser nulo.
  • Uma vez vinculado, não pode mudar.
  • Os chamadores não precisam usar explicitamente &. Isso às vezes é considerado ruim porque você deve ir para a implementação da função para ver se seu parâmetro foi modificado.
Germán Diago
fonte
Um pequeno ponto para aqueles que não sabem: nullptr ou NULL é simplesmente uma 0. stackoverflow.com/questions/462165/...
Serguei Fedorov
2
nullptr não é igual a 0. Tente int a = nullptr; stackoverflow.com/questions/1282295/what-exactly-is-nullptr
Johan Lundberg
0

Uma referência é semelhante a um ponteiro, exceto que você não precisa usar um prefixo ∗ para acessar o valor referido pela referência. Além disso, não é possível fazer uma referência para se referir a um objeto diferente após sua inicialização.

As referências são particularmente úteis para especificar argumentos de função.

para obter mais informações, consulte "Um tour pelo C ++" de "Bjarne Stroustrup" (2014) Páginas 11-12

amirfg
fonte