Pointeur vs référence


256

Quelle serait la meilleure pratique pour attribuer à une fonction la variable d'origine avec laquelle travailler:

unsigned long x = 4;

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

ou:

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

IOW: Y a-t-il une raison de choisir l'un plutôt que l'autre?


1
Les références sont bien sûr précieuses, mais je viens de C, où les pointeurs sont partout. Il faut d'abord maîtriser les pointeurs pour comprendre la valeur des références.
Jay D

Comment cela s'inscrit-il dans un objectif tel que la transparence référentielle de la programmation fonctionnelle? Que se passe-t-il si vous souhaitez toujours que les fonctions renvoient de nouveaux objets et ne modifient jamais l'état en interne, en particulier pas les variables transmises à la fonction. Existe-t-il un moyen d'utiliser ce concept avec des pointeurs et des références dans un langage comme C ++. (Remarque, je suppose que quelqu'un a déjà pour objectif la transparence référentielle. Je ne souhaite pas savoir si c'est un bon objectif à avoir.)
ely

Préférez les références. Des pointeurs utilisateurs lorsque vous n'avez pas le choix.
Ferruccio

Réponses:


285

Ma règle d'or est la suivante:

Utilisez des pointeurs si vous souhaitez effectuer une arithmétique de pointeur avec eux (par exemple, incrémenter l'adresse du pointeur pour parcourir un tableau) ou si vous devez passer un pointeur NULL.

Sinon, utilisez des références.


10
Excellent point concernant un pointeur étant NULL. Si vous avez un paramètre de pointeur, vous devez soit vérifier explicitement qu'il n'est pas NULL, soit rechercher toutes les utilisations de la fonction pour vous assurer qu'il n'est jamais NULL. Cet effort n'est pas requis pour les références.
Richard Corden

26
Expliquez ce que vous entendez par arithmétique. Un nouvel utilisateur peut ne pas comprendre que vous souhaitez ajuster ce que pointe le pointeur.
Martin York du

7
Martin, Par arithmétique, je veux dire que vous passez un pointeur sur une structure mais que vous savez que ce n'est pas une structure simple mais un tableau de celle-ci. Dans ce cas, vous pouvez soit l'indexer en utilisant [], soit faire de l'arithmétique en utilisant ++ / - sur le pointeur. Voilà la différence en un mot.
Nils Pipenbrinck

2
Martin, vous ne pouvez le faire qu'avec des pointeurs directement. Pas avec des références. Bien sûr, vous pouvez prendre un pointeur sur une référence et faire la même chose dans la pratique, mais si vous le faites, vous vous retrouvez avec un code très sale ..
Nils Pipenbrinck

1
Qu'en est-il du polymorphisme (par exemple Base* b = new Derived())? Cela semble être un cas qui ne peut être traité sans pointeurs.
Chris Redford

72

Je pense vraiment que vous bénéficierez de l'établissement des directives de codage des fonctions suivantes:

  1. Comme dans tous les autres endroits, soyez toujours constcorrect.

    • Remarque: Cela signifie, entre autres, que seules les valeurs de sortie (voir point 3) et les valeurs passées par valeur (voir point 4) peuvent ne pas avoir le constspécificateur.
  2. Ne passez une valeur par pointeur que si la valeur 0 / NULL est une entrée valide dans le contexte actuel.

    • Justification 1: En tant qu'appelant , vous voyez que tout ce que vous passez doit être dans un état utilisable.

    • Justification 2: Comme appelé , vous savez que tout ce qui entre est dans un état utilisable. Par conséquent, aucune vérification NULL ou gestion des erreurs ne doit être effectuée pour cette valeur.

    • Justification 3: Les rationnels 1 et 2 seront appliquées par le compilateur . Attrapez toujours les erreurs au moment de la compilation si vous le pouvez.

  3. Si un argument de fonction est une valeur de sortie, passez-le par référence.

    • Justification: Nous ne voulons pas casser le point 2 ...
  4. Choisissez "passer par valeur" sur "passer par référence const" uniquement si la valeur est un POD ( Plain old Datastructure ) ou assez petit (en mémoire) ou d'une autre manière assez bon marché (en temps) pour copier.

    • Justification: éviter les copies inutiles.
    • Remarque: assez petit et assez bon marché ne sont pas des valeurs mesurables absolues.

Il manque la directive quand: ... "quand utiliser const &" ... La directive 2 doit être écrite "pour les valeurs [en], ne passez par le pointeur que si NULL est valide. Sinon, utilisez const reference (ou pour" petits "objets, copie), ou référence s'il s'agit d'une valeur [out]. Je surveille ce post pour ajouter potentiellement un +1.
paercebal

Le point 1 couvre le cas que vous décrivez.
Johann Gerell

Il est un peu difficile de passer un paramètre de sortie par référence s'il n'est pas constructible par défaut. C'est assez courant dans mon code - toute la raison pour laquelle une fonction crée cet objet est parce qu'elle n'est pas triviale.
MSalters le

@MSalters: Si vous allez allouer la mémoire à l'intérieur de la fonction (ce que je pense que c'est ce que vous voulez dire), alors pourquoi ne pas simplement retourner un pointeur sur la mémoire allouée?
Kleist

@Kleist: Au nom de @MSalters, il y a plusieurs raisons possibles. La première est que vous avez peut-être déjà alloué de la mémoire à remplir, comme un pré-dimensionné std::vector<>.
Johann Gerell

24

Cela finit finalement par être subjectif. La discussion jusqu'à présent est utile, mais je ne pense pas qu'il y ait de réponse correcte ou décisive à cela. Beaucoup dépendra des directives de style et de vos besoins à l'époque.

Bien qu'il existe différentes capacités (que quelque chose puisse être NULL ou non) avec un pointeur, la plus grande différence pratique pour un paramètre de sortie est purement syntaxique. Le Guide de style C ++ de Google ( https://google.github.io/styleguide/cppguide.html#Reference_Arguments ), par exemple, n'impose que des pointeurs pour les paramètres de sortie et n'autorise que les références const. Le raisonnement est celui de la lisibilité: quelque chose avec une syntaxe de valeur ne devrait pas avoir de signification sémantique de pointeur. Je ne dis pas que c'est nécessairement vrai ou faux, mais je pense que le point ici est que c'est une question de style, pas de correction.


Que signifie que les références ont une syntaxe de valeur mais une signification sémantique de pointeur?
Eric Andrew Lewis

Il semble que vous passiez une copie car la partie "passer par référence" n'apparaît que dans la définition de la fonction (syntaxe de la valeur), mais vous ne copiez pas la valeur que vous passez, vous passez essentiellement un pointeur sous le capot, ce qui permet la fonction pour modifier votre valeur.
phant0m

Il ne faut pas oublier que le guide de style Google C ++ est très détesté.
Déduplicateur

7

Vous devez passer un pointeur si vous souhaitez modifier la valeur de la variable. Même si le passage technique d'une référence ou d'un pointeur est le même, le passage d'un pointeur dans votre cas d'utilisation est plus lisible car il "annonce" le fait que la valeur sera modifiée par la fonction.


2
Si vous suivez les directives de Johann Gerell, une référence non constante annonce également une variable modifiable, donc le pointeur n'a pas cet avantage ici.
Alexander Kondratskiy

4
@AlexanderKondratskiy: vous manquez le point ... vous ne pouvez pas voir instantanément sur le site d'appel si la fonction appelée accepte un paramètre comme référence constou non const, mais vous pouvez voir si le paramètre a passé ala &xvs x, et utiliser cette convension pour coder si le paramètre est susceptible d'être modifié. (Cela dit, il y a des moments où vous voudrez passer un constpointeur, donc la convension n'est qu'un indice. ....)
Tony Delroy

5

Si vous avez un paramètre où vous devrez peut-être indiquer l'absence de valeur, il est courant de faire du paramètre une valeur de pointeur et de passer NULL.

Une meilleure solution dans la plupart des cas (du point de vue de la sécurité) consiste à utiliser boost :: facultatif . Cela vous permet de passer des valeurs facultatives par référence et également comme valeur de retour.

// 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);
}


4

Pointeurs

  • Un pointeur est une variable qui contient une adresse mémoire.
  • Une déclaration de pointeur se compose d'un type de base, d'un * et du nom de variable.
  • Un pointeur peut pointer vers n'importe quel nombre de variables au cours de la vie
  • Un pointeur qui ne pointe pas actuellement vers un emplacement de mémoire valide reçoit la valeur null (qui est zéro)

    BaseType* ptrBaseType;
    BaseType objBaseType;
    ptrBaseType = &objBaseType;
  • Le & est un opérateur unaire qui renvoie l'adresse mémoire de son opérande.

  • L'opérateur de déréférencement (*) est utilisé pour accéder à la valeur stockée dans la variable vers laquelle pointe le pointeur.

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

Référence

  • Une référence (&) est comme un alias vers une variable existante.

  • Une référence (&) est comme un pointeur constant qui est automatiquement déréférencé.

  • Il est généralement utilisé pour les listes d'arguments de fonction et les valeurs de retour de fonction.

  • Une référence doit être initialisée lors de sa création.

  • Une fois qu'une référence est initialisée à un objet, elle ne peut pas être modifiée pour faire référence à un autre objet.

  • Vous ne pouvez pas avoir de références NULL.

  • Une référence const peut faire référence à un const int. Cela se fait avec une variable temporaire avec la valeur de la 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

entrez la description de l'image ici

entrez la description de l'image ici


3

Une référence est un pointeur implicite. Fondamentalement, vous pouvez modifier la valeur vers laquelle les points de référence sont définis, mais vous ne pouvez pas modifier la référence pour pointer vers autre chose. Donc, mon 2 cents est que si vous voulez seulement changer la valeur d'un paramètre, passez-le comme référence mais si vous devez changer le paramètre pour pointer vers un objet différent, passez-le en utilisant un pointeur.


3

Considérez le mot clé out de C #. Le compilateur requiert que l'appelant d'une méthode applique le mot clé out à tous les arguments out, même s'il sait déjà s'ils le sont. Ceci est destiné à améliorer la lisibilité. Bien qu'avec les IDE modernes, je suis enclin à penser que c'est un travail de mise en évidence syntaxique (ou sémantique).


faute de frappe: sémantique, pas symétrique; +1 Je suis d'accord sur la possibilité de surligner au lieu d'écrire (C #), ou & (dans le cas de C, pas de références)
peenut

2

Passez par référence const sauf s'il y a une raison pour laquelle vous souhaitez modifier / conserver le contenu que vous transmettez.

Ce sera la méthode la plus efficace dans la plupart des cas.

Assurez-vous que vous utilisez const sur chaque paramètre que vous ne souhaitez pas modifier, car cela vous protège non seulement de faire quelque chose de stupide dans la fonction, mais il donne une bonne indication aux autres utilisateurs de ce que la fonction fait aux valeurs transmises. Cela comprend la création d'un pointeur const lorsque vous ne souhaitez modifier que ce qui est indiqué ...


2

Pointeurs:

  • Peut être assigné nullptr (ouNULL ).
  • Sur le site de l'appel, vous devez utiliser & si votre type n'est pas un pointeur lui-même, ce qui signifie explicitement que vous modifiez votre objet.
  • Les pointeurs peuvent être rebondis.

Références:

  • Ne peut pas être nulle.
  • Une fois lié, ne peut pas changer.
  • Les appelants n'ont pas besoin d'utiliser explicitement &. Ceci est parfois considéré comme mauvais car vous devez aller à l'implémentation de la fonction pour voir si votre paramètre est modifié.

Un petit point pour ceux qui ne savent pas: nullptr ou NULL est tout simplement un 0. stackoverflow.com/questions/462165/…
Serguei Fedorov

2
nullptr n'est pas identique à 0. Essayez int a = nullptr; stackoverflow.com/questions/1282295/what-exactly-is-nullptr
Johan Lundberg

0

Une référence est similaire à un pointeur, sauf que vous n'avez pas besoin d'utiliser un préfixe ∗ pour accéder à la valeur référencée par la référence. De même, il n'est pas possible de faire référence à un objet différent après son initialisation.

Les références sont particulièrement utiles pour spécifier des arguments de fonction.

pour plus d'informations, voir "A Tour of C ++" de "Bjarne Stroustrup" (2014) Pages 11-12

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.