Raison de passer un pointeur par référence en C ++?


97

Dans quelles circonstances voudriez-vous utiliser du code de cette nature en C ++?

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

void fii() {
  type *choochoo;
  ...
  foo(choochoo);
}

si vous avez besoin de renvoyer un pointeur - mieux utiliser une valeur de retour
littleadv

6
Pouvez-vous expliquer pourquoi? Cette recommandation n'est pas très utile. Ma question est tout à fait légitime. Ceci est actuellement utilisé dans le code de production. Je ne comprends tout simplement pas pourquoi.
Matthew Hoggan

1
David résume assez bien la situation. Le pointeur lui-même est en cours de modification.
chris

2
Je passe par là si j'appelle l'opérateur "Nouveau" dans la fonction.
EMI du

Réponses:


142

Vous souhaiterez passer un pointeur par référence si vous avez besoin de modifier le pointeur plutôt que l'objet vers lequel le pointeur pointe.

Ceci est similaire à la raison pour laquelle des pointeurs doubles sont utilisés; l'utilisation d'une référence à un pointeur est légèrement plus sûre que l'utilisation de pointeurs.


14
Donc similaire à un pointeur d'un pointeur, sauf un niveau d'indirection de moins pour la sémantique passe-par-référence?

Je voudrais ajouter que parfois, vous souhaitez également renvoyer un pointeur par référence. Par exemple, si vous avez une classe contenant des données dans un tableau alloué dynamiquement, mais que vous souhaitez fournir un accès (non constant) à ces données au client. Dans le même temps, vous ne voulez pas que le client puisse manipuler la mémoire via le pointeur.

2
@William pouvez-vous élaborer là-dessus? Ça semble intéressant. Cela signifie-t-il que l'utilisateur de cette classe pourrait obtenir un pointeur vers le tampon de données interne et y lire / écrire, mais il ne peut pas - par exemple - libérer ce tampon en utilisant deleteou relier ce pointeur à un autre endroit de la mémoire? Ou est-ce que je me suis trompé?
BarbaraKwarc

2
@BarbaraKwarc Bien sûr. Supposons que vous ayez une implémentation simple d'un tableau dynamique. Naturellement, vous surchargerez l' []opérateur. Une signature possible (et en fait naturelle) pour cela serait const T &operator[](size_t index) const. Mais vous pourriez aussi avoir T &operator[](size_t index). Vous pourriez avoir les deux en même temps. Ce dernier vous permettrait de faire des choses comme myArray[jj] = 42. En même temps, vous ne fournissez pas de pointeur vers vos données, de sorte que l'appelant ne peut pas jouer avec la mémoire (par exemple, supprimez-la accidentellement).

Oh. Je pensais que vous parliez de renvoyer une référence à un pointeur (votre libellé exact: "renvoyer un pointeur par référence", parce que c'était ce à quoi OP demandait: passer des pointeurs par des références, pas simplement de vieilles références) comme une façon élégante de donner un accès en lecture / écriture au tampon sans permettre de manipuler le tampon lui-même (par exemple en le libérant). Renvoyer une simple référence ancienne est une chose différente et je le sais.
BarbaraKwarc

65

50% des programmeurs C ++ aiment définir leurs pointeurs sur null après une suppression:

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

Sans la référence, vous ne modifieriez qu'une copie locale du pointeur, sans affecter l'appelant.


19
J'aime le nom; )
4pie0

2
J'échangerai le son stupide pour trouver la réponse: pourquoi est-ce que cela s'appelle supprimer stupide? Qu'est-ce que je rate?
Sammaron

1
@Sammaron Si vous regardez l'historique, le modèle de fonction était appelé paranoid_delete, mais Puppy l'a renommé moronic_delete. Il appartient probablement aux 50% restants;) Quoi qu'il en soit, la réponse courte est que les paramètres pointeurs vers null après ne deletesont presque jamais utiles (car ils devraient sortir de la portée, de toute façon) et gênent souvent la détection de bogues "use after free".
fredoverflow

@fredoverflow Je comprends votre réponse, mais @Puppy semble penser que deletec'est stupide en général. Chiot, j'ai fait quelques recherches sur Google, je ne vois pas pourquoi la suppression est complètement inutile (peut-être que je suis aussi un terrible googleur;)). Pouvez-vous expliquer un peu plus ou fournir un lien?
Sammaron

@Sammaron, C ++ a maintenant des "pointeurs intelligents" qui encapsulent des pointeurs réguliers et appellent automatiquement "delete" pour vous lorsqu'ils sont hors de portée. Les pointeurs intelligents sont largement préférés aux pointeurs stupides de style C car ils éliminent complètement ce problème.
tjwrona1992

14

La réponse de David est correcte, mais si elle est encore un peu abstraite, voici deux exemples:

  1. Vous souhaiterez peut-être remettre à zéro tous les pointeurs libérés pour détecter les problèmes de mémoire plus tôt. C-style que vous feriez:

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

    En C ++ pour faire de même, vous pouvez faire:

    template<class T> void freeAndZero(T* &ptr)
    {
        delete ptr;
        ptr = 0;
    }
    
    int* ptr = new int;
    
    ...
    
    freeAndZero(ptr);
  2. Lorsqu'il s'agit de listes chaînées - souvent simplement représentées comme des pointeurs vers un nœud suivant:

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

    Dans ce cas, lorsque vous insérez dans la liste vide, vous devez nécessairement changer le pointeur entrant car le résultat n'est plus le NULLpointeur. C'est un cas où vous modifiez un pointeur externe à partir d'une fonction, afin qu'il ait une référence au pointeur dans sa signature:

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

Il y a un exemple dans cette question .


8

J'ai dû utiliser un code comme celui-ci pour fournir des fonctions permettant d'allouer de la mémoire à un pointeur passé et de renvoyer sa taille parce que ma société "m'objecte" en utilisant la STL

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

Ce n'est pas sympa, mais le pointeur doit être passé par référence (ou utiliser un double pointeur). Sinon, la mémoire est allouée à une copie locale du pointeur si elle est passée par valeur, ce qui entraîne une fuite de mémoire.


2

Un exemple est lorsque vous écrivez une fonction d'analyseur et lui passez un pointeur source à lire, si la fonction est censée pousser ce pointeur en avant derrière le dernier caractère qui a été correctement reconnu par l'analyseur. L'utilisation d'une référence à un pointeur indique clairement que la fonction déplacera le pointeur d'origine pour mettre à jour sa position.

En général, vous utilisez des références à des pointeurs si vous souhaitez transmettre un pointeur à une fonction et le laisser déplacer ce pointeur d' origine vers une autre position au lieu de simplement en déplacer une copie sans affecter l'original.


Pourquoi le vote négatif? Y a-t-il quelque chose qui ne va pas avec ce que j'ai écrit? Soin d'expliquer? : P
BarbaraKwarc

0

Une autre situation où vous pourriez en avoir besoin est si vous avez une collection stl de pointeurs et que vous souhaitez les modifier à l'aide de l'algorithme stl. Exemple de for_each en 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();
  } 

};

Sinon, si vous utilisez la signature changeObject (Object * item), vous avez une copie du pointeur, pas l'original.


c'est plus un "objet de remplacement" qu'un "objet de changement" - il y a une différence
serup
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.