Razão para passar um ponteiro por referência em C ++?

97

Sob quais circunstâncias você desejaria usar código dessa natureza em c ++?

void foo(type *&in) {...}

void fii() {
  type *choochoo;
  ...
  foo(choochoo);
}
Matthew Hoggan
fonte
se você precisar retornar um ponteiro - melhor usar um valor de retorno
littleadv de
6
Você pode explicar por quê? Este elogio não é muito útil. Minha pergunta é bastante legítima. Isso está sendo usado no código de produção. Eu simplesmente não entendo completamente o porquê.
Matthew Hoggan
1
David resume muito bem. O próprio ponteiro está sendo modificado.
chris de
2
Eu passo por aqui se vou chamar o operador "Novo" na função.
NDEthos

Respostas:

142

Você gostaria de passar um ponteiro por referência se precisar modificar o ponteiro em vez do objeto para o qual o ponteiro está apontando.

Isso é semelhante ao motivo pelo qual ponteiros duplos são usados; usar uma referência a um ponteiro é ligeiramente mais seguro do que usar ponteiros.

David Z.
fonte
14
Tão semelhante a um ponteiro de um ponteiro, exceto um nível a menos de indireção para a semântica de passagem por referência?
Eu gostaria de acrescentar que às vezes você também deseja retornar um ponteiro por referência. Por exemplo, se você tem uma classe contendo dados em um array alocado dinamicamente, mas deseja fornecer ao cliente acesso (não constante) a esses dados. Ao mesmo tempo, você não deseja que o cliente consiga manipular a memória por meio do ponteiro.
2
@William você pode elaborar sobre isso? Parece interessante. Isso significa que o usuário dessa classe pode obter um ponteiro para o buffer de dados interno e ler / gravar nele, mas não pode - por exemplo - liberar esse buffer usando deleteou religar esse ponteiro para algum outro lugar na memória? Ou eu entendi errado?
BarbaraKwarc
2
@BarbaraKwarc Claro. Digamos que você tenha uma implementação simples de um array dinâmico. Naturalmente, você sobrecarregará o []operador. Uma assinatura possível (e de fato natural) para isso seria const T &operator[](size_t index) const. Mas você também poderia ter T &operator[](size_t index). Você poderia ter os dois ao mesmo tempo. Este último permite que você faça coisas como myArray[jj] = 42. Ao mesmo tempo, você não está fornecendo um ponteiro para seus dados, então o chamador não pode mexer com a memória (por exemplo, excluí-la acidentalmente).
Oh. Achei que você estivesse falando sobre retornar uma referência a um ponteiro (sua redação exata: "retornar um ponteiro por referência", porque era isso que OP estava perguntando: passar ponteiros por referências, não apenas referências antigas simples) como uma forma sofisticada de dar acesso de leitura / gravação ao buffer sem permitir manipular o próprio buffer (por exemplo, liberando-o). Retornar uma referência simples e antiga é uma coisa diferente e eu sei disso.
BarbaraKwarc
65

50% dos programadores C ++ gostam de definir seus ponteiros como nulos após uma exclusão:

template<typename T>    
void moronic_delete(T*& p)
{
    delete p;
    p = nullptr;
}

Sem a referência, você apenas mudaria uma cópia local do ponteiro, não afetando o chamador.

fredoverflow
fonte
19
Gosto do nome; )
4pie0 de
2
Vou trocar soando estúpido por descobrir a resposta: por que isso é chamado de deletar idiota? o que estou perdendo?
Sammaron
1
@Sammaron Se você olhar o histórico, o modelo de função costumava ser chamado paranoid_delete, mas o Puppy o renomeou para moronic_delete. Ele provavelmente pertence aos outros 50%;) De qualquer forma, a resposta curta é que os ponteiros de configuração para nulo depois deletequase nunca são úteis (porque eles deveriam sair do escopo, de qualquer maneira) e muitas vezes dificultam a detecção de bugs de "uso após livre".
fredoverflow
@fredoverflow Eu entendo sua resposta, mas @Puppy parece pensar que deleteé estúpido em geral. Cachorrinho, eu fiz algumas pesquisas no Google, não consigo ver porque deletar é completamente inútil (talvez eu seja um péssimo googler também;)). Você pode explicar um pouco mais ou fornecer um link?
Sammaron,
@ Sammaron, C ++ tem "ponteiros inteligentes" agora que encapsulam ponteiros regulares e chamam automaticamente "delete" para você quando eles saem do escopo. Os ponteiros inteligentes são amplamente preferidos aos ponteiros idiotas do estilo C porque eliminam esse problema por completo.
tjwrona1992
14

A resposta de David está correta, mas se ainda for um pouco abstrata, aqui estão dois exemplos:

  1. Você pode querer zerar todos os ponteiros liberados para detectar problemas de memória mais cedo. Você faria no estilo C:

    void freeAndZero(void** ptr)
    {
        free(*ptr);
        *ptr = 0;
    }
    
    void* ptr = malloc(...);
    
    ...
    
    freeAndZero(&ptr);

    Em C ++, para fazer o mesmo, você pode fazer:

    template<class T> void freeAndZero(T* &ptr)
    {
        delete ptr;
        ptr = 0;
    }
    
    int* ptr = new int;
    
    ...
    
    freeAndZero(ptr);
  2. Ao lidar com listas vinculadas - frequentemente representadas simplesmente como ponteiros para um próximo nó:

    struct Node
    {
        value_t value;
        Node* next;
    };

    Neste caso, quando você insere na lista vazia, você deve necessariamente alterar o ponteiro de entrada porque o resultado não é mais o NULLponteiro. Este é o caso em que você modifica um ponteiro externo de uma função, então ele teria uma referência a um ponteiro em sua assinatura:

    void insert(Node* &list)
    {
        ...
        if(!list) list = new Node(...);
        ...
    }

Há um exemplo nesta questão .

Antoine
fonte
8

Eu tive que usar um código como este para fornecer funções para alocar memória a um ponteiro passado e retornar seu tamanho porque minha empresa "objeto" para mim usando o STL

 int iSizeOfArray(int* &piArray) {
    piArray = new int[iNumberOfElements];
    ...
    return iNumberOfElements;
 }

Não é legal, mas o ponteiro deve ser passado por referência (ou use ponteiro duplo). Caso contrário, a memória é alocada para uma cópia local do ponteiro se ele for passado por valor, o que resulta em um vazamento de memória.

matemático 1975
fonte
2

Um exemplo é quando você escreve uma função de analisador e passa a ela um ponteiro de origem para leitura, se a função deve empurrar esse ponteiro para frente atrás do último caractere que foi corretamente reconhecido pelo analisador. Usar uma referência a um ponteiro torna claro que a função moverá o ponteiro original para atualizar sua posição.

Em geral, você usa referências a ponteiros se quiser passar um ponteiro para uma função e deixá-lo mover o ponteiro original para alguma outra posição em vez de apenas mover uma cópia dele sem afetar o original.

BarbaraKwarc
fonte
Por que o downvote? Há algo errado com o que escrevi? Importa-se de explicar? : P
BarbaraKwarc
0

Outra situação em que você pode precisar disso é se você tiver uma coleção de ponteiros stl e quiser alterá-los usando o algoritmo stl. Exemplo de for_each em c ++ 98.

struct Storage {
  typedef std::list<Object*> ObjectList;
  ObjectList objects;

  void change() {
    typedef void (*ChangeFunctionType)(Object*&);
    std::for_each<ObjectList::iterator, ChangeFunctionType>
                 (objects.begin(), objects.end(), &Storage::changeObject);
  }

  static void changeObject(Object*& item) {
    delete item;
    item = 0;
    if (someCondition) item = new Object();
  } 

};

Caso contrário, se você usar a assinatura changeObject (Object * item), terá uma cópia do ponteiro, não o original.

Nove
fonte
este é mais um "objeto de substituição" do que um "objeto de mudança" - há uma diferença
serup