web-dev-qa-db-fra.com

Quand utiliser les références et les pointeurs

Je comprends la syntaxe et la sémantique générale des pointeurs par rapport aux références, mais comment devrais-je décider quand il est plus ou moins approprié d'utiliser des références ou des pointeurs dans une API?

Naturellement, certaines situations nécessitent l'une ou l'autre (operator++ nécessite un argument de référence), mais en général, je préfère utiliser des pointeurs (et des pointeurs const) car la syntaxe indique clairement que les variables sont transmises de manière destructive.

Par exemple. dans le code suivant:

void add_one(int& n) { n += 1; }
void add_one(int* const n) { *n += 1; }
int main() {
  int a = 0;
  add_one(a); // Not clear that a may be modified
  add_one(&a); // 'a' is clearly being passed destructively
}

Avec le pointeur, il est toujours (plus) évident de savoir ce qui se passe, alors pour les API et autres où la clarté est une préoccupation majeure, les pointeurs ne sont-ils pas plus appropriés que les références? Cela signifie-t-il que les références ne doivent être utilisées que lorsque cela est nécessaire (par exemple, operator++)? Existe-t-il des problèmes de performance avec l'un ou l'autre?

EDIT (OUTDATED):

En plus de permettre les valeurs NULL et de traiter les tableaux bruts, il semble que le choix dépende de vos préférences personnelles. J'ai accepté la réponse ci-dessous qui fait référence à Le Guide de style C++ de Google , car ils présentent le point de vue suivant: "Les références peuvent être source de confusion, car elles ont une syntaxe de valeur mais une sémantique de pointeur".

En raison du travail supplémentaire requis pour assainir les arguments de pointeur qui ne doivent pas être NULL (par exemple, add_one(0) appellera la version du pointeur et sera interrompu au moment de l'exécution), il est logique d'utiliser des références lorsqu'un objet DOIT être présent, dommage de perdre la clarté syntaxique.

346
connec

Utilisez des références partout où vous le pouvez, des pointeurs partout où vous en avez besoin.

Évitez les pointeurs tant que vous ne le pouvez pas.

La raison en est que les pointeurs rendent les choses plus difficiles à suivre/à lire, les manipulations moins sûres et beaucoup plus dangereuses que tout autre concept.

Donc, la règle de base est d'utiliser des pointeurs uniquement s'il n'y a pas d'autre choix. 

Par exemple, renvoyer un pointeur sur un objet est une option valide lorsque la fonction peut renvoyer nullptr dans certains cas et il est supposé que ce sera le cas. Cela dit, une meilleure option serait d’utiliser quelque chose de similaire à boost::optional.

Un autre exemple consiste à utiliser des pointeurs sur la mémoire brute pour des manipulations de mémoire spécifiques. Cela devrait être caché et localisé dans des parties très étroites du code, pour aider à limiter les parties dangereuses de la base de code entière. 

Dans votre exemple, il ne sert à rien d'utiliser un pointeur comme argument car:

  1. si vous fournissez nullptr comme argument, vous vous retrouvez dans un comportement indéfini;
  2. la version d'attribut de référence ne permet pas (sans facile de repérer des astuces) le problème avec 1.
  3. la version de l'attribut de référence est plus simple à comprendre pour l'utilisateur: vous devez fournir un objet valide, et non quelque chose qui pourrait être null.

Si le comportement de la fonction doit fonctionner avec ou sans un objet donné, l'utilisation d'un pointeur en tant qu'attribut suggère que vous pouvez passer nullptr en tant qu'argument et que cela convient à la fonction. C'est un peu un contrat entre l'utilisateur et l'implémentation.

264
Klaim

Les performances sont exactement les mêmes, les références étant implémentées en interne en tant que pointeurs. Ainsi, vous n'avez pas besoin de vous inquiéter à ce sujet.

Il n'y a pas de convention généralement acceptée concernant le moment d'utiliser des références et des pointeurs. Dans quelques cas, vous devez renvoyer ou accepter des références (constructeur de copie, par exemple), mais vous êtes libre de le faire. Une convention assez courante que j'ai rencontrée consiste à utiliser des références lorsque le paramètre doit faire référence à un objet existant et à des pointeurs lorsqu'une valeur NULL est ok.

Certaines conventions de codage (telles que Google ) imposent de toujours utiliser des pointeurs ou des références const, car les références ont une syntaxe peu claire: elles ont un comportement de référence mais une syntaxe de valeur.

56
Andrea Bergia

De C++ FAQ Lite

Utilisez des références lorsque vous le pouvez et des pointeurs lorsque vous devez le faire.

Les références sont généralement préférées aux pointeurs chaque fois que vous n'en avez pas besoin "repositionner". Cela signifie généralement que les références sont les plus utiles dans un interface publique de la classe. Les références apparaissent généralement sur la peau de un objet et des pointeurs à l'intérieur.

L'exception à ce qui précède concerne les paramètres ou les retours d'une fonction valeur nécessite une référence "sentinelle" - une référence qui ne fait pas référence à un objet. Ceci est généralement mieux fait en retournant/en prenant un pointeur, et donner au pointeur NULL cette signification particulière (les références doivent toujours des objets alias, pas un pointeur NULL déréférencé).

Remarque: les anciens programmeurs de la ligne C n'aiment parfois pas les références depuis ils fournissent une sémantique de référence qui n'est pas explicite dans le .__ de l'appelant. code. Après quelques expériences en C++, on se rend vite compte que c’est une forme de dissimulation d'informations, qui est un atout plutôt qu'un responsabilité. Par exemple, les programmeurs doivent écrire du code dans le langage du problème plutôt que la langue de la machine.

31
Mahesh

Ma règle de base est:

  • Utilisez des pointeurs pour les paramètres sortant ou entrant/sortant. On voit donc que la valeur va changer. (Vous devez utiliser &)
  • Utilisez des pointeurs si le paramètre NULL est une valeur acceptable. (Assurez-vous que c'est const s'il s'agit d'un paramètre entrant)
  • Utilisez des références pour le paramètre incoming s'il ne peut pas être NULL et qu'il ne s'agit pas d'un type primitif (const T&).
  • Utilisez des pointeurs ou des pointeurs intelligents lors du renvoi d'un objet nouvellement créé.
  • Utilisez des pointeurs ou des pointeurs intelligents comme membres de structure ou de classe au lieu de références.
  • Utiliser des références pour le crénelage (par exemple, int &current = someArray[i])

Indépendamment de celui que vous utilisez, n'oubliez pas de documenter vos fonctions et la signification de leurs paramètres si cela n'est pas évident.

14
Calmarius

Comme les autres qui ont déjà répondu: utilisez toujours des références, à moins que la variable étant NULLnullptr ne soit _/vraiment un état valide.

Le point de vue de John Carmack sur le sujet est similaire:

Les pointeurs NULL sont le plus gros problème en C/C++, du moins dans notre code. La double utilisation d'une valeur unique en tant qu'indicateur et adresse provoque un nombre incroyable de problèmes fatals. Les références C++ doivent être privilégiées par rapport aux pointeurs, dans la mesure du possible. alors qu'une référence est "vraiment" juste un pointeur, elle a le contrat implicite d'être non-NULL. Effectuer des contrôles NULL lorsque les pointeurs sont transformés en références, vous pouvez ensuite ignorer le problème.

http://www.altdevblogaday.com/2011/12/24/static-code-analysis/

Edit 2012-03-13

Utilisateur Bret Kuhns remarque à juste titre:

La norme C++ 11 a été finalisée. Je pense qu'il est temps dans ce fil de mentionner que la plupart du code devrait fonctionner parfaitement avec une combinaison de références, shared_ptr et unique_ptr.

C'est assez vrai, mais la question demeure, même lors du remplacement de pointeurs bruts par des pointeurs intelligents.

Par exemple, std::unique_ptr et std::shared_ptr peuvent être construits comme des pointeurs "vides" via leur constructeur par défaut:

... signifiant que les utiliser sans vérifier qu'ils ne sont pas vides risque de provoquer un crash, ce qui est exactement le propos de la discussion de J. Carmack.

Et puis, nous avons le problème amusant de "comment passer un pointeur intelligent comme paramètre de fonction?"

Jon / s answer pour la question C++ - passage de références à boost :: shared_ptr , et les commentaires suivants montrent que même dans ce cas, passer un pointeur intelligent par copie ou par référence n'est C’est clair comme on voudrait (je privilégie par défaut la "référence", mais je peux me tromper).

12
paercebal

Avertissement: mis à part le fait que les références ne peuvent être ni NULL ni "rebond" (ce qui signifie qu'elles ne peuvent pas changer l'objet dont elles sont l'alias), il s'agit vraiment d'une question de goût, je ne vais donc pas dire "c'est mieux".

Cela dit, je ne suis pas d'accord avec votre dernière déclaration dans le post, en ce sens que je ne pense pas que le code perd de la clarté avec les références. Dans votre exemple

add_one(&a);

pourrait être plus clair que

add_one(a);

puisque vous savez que très probablement la valeur de a va changer. Par contre, la signature de la fonction

void add_one(int* const n);

est quelque peu pas clair non plus: est-ce que n va être un entier simple ou un tableau? Parfois, vous n'avez accès qu'aux en-têtes (mal documentés) et aux signatures telles que

foo(int* const a, int b);

ne sont pas faciles à interpréter à première vue.

A mon avis, les références sont aussi bonnes que les pointeurs lorsqu'aucune (ré) allocation ni nouvelle liaison (au sens expliqué précédemment) n'est nécessaire. De plus, si un développeur utilise uniquement des pointeurs pour des tableaux, les signatures de fonctions sont un peu moins ambiguës. Sans oublier le fait que la syntaxe des opérateurs est beaucoup plus lisible avec des références.

12
bartgol

Ce n'est pas une question de goût. Voici quelques règles définitives.

Si vous souhaitez faire référence à une variable déclarée de manière statique dans la portée dans laquelle elle a été déclarée, utilisez une référence C++ et elle sera parfaitement sûre. La même chose s'applique à un pointeur intelligent déclaré de manière statique. Le passage de paramètres par référence est un exemple de cette utilisation.

Si vous souhaitez faire référence à un élément d'une étendue plus large que celle dans laquelle il a été déclaré, vous devez utiliser un pointeur intelligent de comptage de références pour qu'il soit parfaitement sûr.

Vous pouvez faire référence à un élément d'une collection avec une référence pour des raisons de syntaxe, mais ce n'est pas sûr; L'élément peut être supprimé à tout moment.

Pour conserver en toute sécurité une référence à un élément d'une collection, vous devez utiliser un pointeur intelligent compté de références.

7
John Morrison

Toute différence de performance serait si minime qu'elle ne justifierait pas l'utilisation d'une approche moins claire.

Premièrement, un cas non mentionné où les références sont généralement supérieures est const références. Pour les types non simples, passer un const reference évite de créer un temporaire et ne crée pas la confusion qui vous préoccupe (car la valeur n'est pas modifiée). Ici, forcer une personne à passer un pointeur crée la confusion qui vous inquiète, car voir l'adresse prise et transmise à une fonction peut vous faire penser que la valeur a changé.

En tout état de cause, je suis essentiellement d'accord avec vous. Je n'aime pas les fonctions prenant des références pour modifier leur valeur quand il n'est pas très évident que c'est ce que fait la fonction. Moi aussi je préfère utiliser des pointeurs dans ce cas.

Lorsque vous devez renvoyer une valeur dans un type complexe, j'ai tendance à préférer les références. Par exemple:

bool GetFooArray(array &foo); // my preference
bool GetFooArray(array *foo); // alternative

Ici, le nom de la fonction indique clairement que vous récupérez des informations dans un tableau. Donc, il n'y a pas de confusion.

Les principaux avantages des références sont qu’elles contiennent toujours une valeur valide, qu’elles sont plus propres que les pointeurs et qu’elles prennent en charge le polymorphisme sans nécessiter de syntaxe supplémentaire. Si aucun de ces avantages ne s'applique, il n'y a aucune raison de préférer une référence à un pointeur.

5
David Schwartz

Copié de wiki -

En conséquence, dans de nombreuses implémentations, l'utilisation d'une variable à durée de vie automatique ou statique via une référence, bien que sa syntaxe soit similaire à son accès direct, peut impliquer des opérations de déréférencement masquées et coûteuses. Les références sont une fonctionnalité de C++ controversée sur le plan de la syntaxe car elles masquent le niveau d'indirection d'un identifiant; c’est-à-dire que, contrairement au code C où les pointeurs sont généralement syntaxiques, dans un grand bloc de code C++, il peut ne pas être immédiatement évident si l’objet auquel on accède est défini en tant que variable locale ou globale ou s’il s’agit d’une référence (pointeur implicite) à un autre emplacement, en particulier si le code mélange des références et des pointeurs. Cet aspect peut rendre plus difficile la lecture et le débogage d'un code C++ mal écrit (voir Aliasing).

Je suis d’accord à 100% avec cela, et c’est la raison pour laquelle je pense que vous ne devriez utiliser une référence que lorsque vous avez une très bonne raison de le faire.

4
user606723

Il y a un problème avec la règle "utiliser des références chaque fois que possible" et cela se produit si vous souhaitez conserver une référence pour une utilisation ultérieure. Pour illustrer cela avec un exemple, imaginez que vous ayez les classes suivantes.

class SimCard
{
    public:
        explicit SimCard(int id):
            m_id(id)
        {
        }

        int getId() const
        {
            return m_id;
        }

    private:
        int m_id;
};

class RefPhone
{
    public:
        explicit RefPhone(const SimCard & card):
            m_card(card)
        {
        }

        int getSimId()
        {
            return m_card.getId();
        }

    private:
        const SimCard & m_card;
};

Au début, il peut sembler être une bonne idée de faire passer un paramètre dans le constructeur RefPhone(const SimCard & card) par une référence, car cela empêche de transmettre des pointeurs incorrects/nuls au constructeur. Cela encourage en quelque sorte l’allocation des variables sur la pile et tire profit de la RAII.

PtrPhone nullPhone(0);  //this will not happen that easily
SimCard * cardPtr = new SimCard(666);  //evil pointer
delete cardPtr;  //muahaha
PtrPhone uninitPhone(cardPtr);  //this will not happen that easily

Mais alors les temporaires viennent détruire votre monde heureux.

RefPhone tempPhone(SimCard(666));   //evil temporary
//function referring to destroyed object
tempPhone.getSimId();    //this can happen

Donc, si vous vous en tenez aveuglément aux références, vous échangez la possibilité de transmettre des pointeurs non valides à la possibilité de stocker des références aux objets détruits, ce qui a fondamentalement le même effet.

edit: Notez que je me suis tenu à la règle "Utilisez référence chaque fois que vous le pouvez, pointeurs partout où vous le devez. Évitez les pointeurs tant que vous ne le pouvez pas." parmi les réponses les plus votées et acceptées (d’autres réponses le suggèrent également). Bien que cela soit évident, l'exemple n'est pas de montrer que les références en tant que telles sont mauvaises. Cependant, ils peuvent être utilisés à mauvais escient, tout comme des pointeurs et ils peuvent apporter leurs propres menaces au code.


Les différences entre les pointeurs et les références sont les suivantes.

  1. Quand il s'agit de passer des variables, passer par référence ressemble à passer par valeur, mais possède une sémantique de pointeur (agit comme un pointeur). 
  2. La référence ne peut pas être directement initialisée à 0 (null). 
  3. Référence (référence, objet non référencé) ne peut pas être modifié (équivalent au pointeur "* const").
  4. la référence const peut accepter un paramètre temporaire.
  5. Les références const locales prolongent la durée de vie des objets temporaires

Tenant compte de cela, mes règles actuelles sont les suivantes.

  • Utilisez des références pour les paramètres qui seront utilisés localement dans une étendue de fonction.
  • Utilisez des pointeurs lorsque 0 (null) est une valeur de paramètre acceptable ou vous devez stocker le paramètre pour une utilisation ultérieure. Si 0 (nul) est acceptable, j'ajoute le suffixe "_n" au paramètre, utilise un pointeur gardé (comme QPointer dans Qt) ou simplement le documenter. Vous pouvez également utiliser des pointeurs intelligents. Vous devez être encore plus prudent avec les pointeurs partagés qu'avec les pointeurs normaux (sinon, vous pouvez vous retrouver avec des fuites de mémoire et des erreurs de responsabilité).
3
doc

Points à garder à l'esprit:

  1. Les pointeurs peuvent être NULL, les références ne peuvent pas être NULL.

  2. Les références sont plus faciles à utiliser, const peut être utilisé pour une référence lorsque nous ne voulons pas changer de valeur et que nous avons simplement besoin d'une référence dans une fonction.

  3. Pointeur utilisé avec un * alors que les références utilisées avec un &.

  4. Utilisez des pointeurs lorsque des opérations arithmétiques de pointeur sont requises. 

  5. Vous pouvez avoir des pointeurs sur un type de vide int a=5; void *p = &a;, mais vous ne pouvez pas faire référence à un type de vide.

Pointer Vs Reference

void fun(int *a)
{
    cout<<a<<'\n'; // address of a = 0x7fff79f83eac
    cout<<*a<<'\n'; // value at a = 5
    cout<<a+1<<'\n'; // address of a increment by 4 bytes(int) = 0x7fff79f83eb0
    cout<<*(a+1)<<'\n'; // value here is by default = 0
}
void fun(int &a)
{
    cout<<a<<'\n'; // reference of original a passed a = 5
}
int a=5;
fun(&a);
fun(a);

Verdict quand utiliser quoi

Pointeur : Pour les implémentations de tableaux, de listes de liens, d'arbres et l'arithmétique de pointeurs.

Référence : Dans les paramètres de fonction et les types de retour.

2
shaurya uppal

Voici quelques lignes directrices.

Une fonction utilise les données passées sans les modifier:

  1. Si l'objet de données est petit, tel qu'un type de données intégré ou une petite structure, transmettez-le par valeur.

  2. Si l’objet de données est un tableau, utilisez un pointeur car c’est votre seul choix. Faites du pointeur un pointeur sur const.

  3. Si l'objet de données est une structure de bonne taille, utilisez un pointeur const ou une référence const Pour augmenter l'efficacité du programme. Vous économisez le temps et l'espace nécessaires pour copier une structure ou une conception de classe. Faites le pointeur ou la référence const.

  4. Si l'objet de données est un objet de classe, utilisez une référence const. La sémantique de la conception de classe nécessite souvent l'utilisation d'une référence, ce qui est la raison principale pour laquelle C++ a ajouté This feature. référence.

Une fonction modifie les données dans la fonction appelante:

1.Si l'objet de données est un type de données intégré, utilisez un pointeur. Si vous voyez du code Comme fixit (& x), où x est un int, il est assez clair que cette fonction a pour but de modifier x.

2.Si l'objet de données est un tableau, utilisez votre seul choix: un pointeur.

3.Si l'objet de données est une structure, utilisez une référence ou un pointeur.

4.Si l'objet de données est un objet de classe, utilisez une référence.

Bien sûr, ce ne sont que des directives et il pourrait y avoir des raisons de faire des choix différents Par exemple, cin utilise des références pour les types de base afin que vous puissiez utiliser cin >> n Au lieu de cin >> & n.

1
Sachin Godara

Les pointeurs et les références ont la même vitesse, et les pointeurs peuvent faire tout ce que les références peuvent faire et plus encore. Il est fortement basé sur l'opinion personnelle que l'on doit utiliser. Il est courant d'utiliser des références sauf si vous avez vraiment besoin de certaines des fonctionnalités des pointeurs.

Je viens de mettre mon argent. Je viens d'effectuer un test. Un sneeky à cela. Je laisse simplement g ++ créer les fichiers d'assemblage du même mini-programme en utilisant des pointeurs par rapport aux références . Lorsque vous regardez le résultat, ils sont exactement les mêmes. Autre que le symbole. Donc, en regardant la performance (dans un exemple simple), il n’ya pas de problème.

Parlons maintenant des pointeurs et des références. IMHO Je pense que la clarté est au-dessus de tout. Dès que je lis le comportement implicite, mes orteils commencent à se courber. Je conviens que le comportement implicite de Nice est qu’une référence ne peut pas être NULL.

Déréférencer un pointeur NULL n'est pas le problème. il va planter votre application et il sera facile à déboguer. Un problème plus important concerne les pointeurs non initialisés contenant des valeurs non valides. Cela entraînera très probablement une corruption de la mémoire, entraînant un comportement indéfini sans une origine claire.

C’est là que je pense que les références sont beaucoup plus sûres que les pointeurs. Et je suis d'accord avec une déclaration précédente, à savoir que l'interface (qui devrait être clairement documentée, voir Conception par contrat, Bertrand Meyer) définit le résultat des paramètres d'une fonction. Maintenant, prenant tout cela en considération, mes préférences vont vers En utilisant des références partout/autant que possible.

0
Tom van den Broek

Les références sont plus propres, plus faciles à utiliser et cachent mieux les informations. Les références ne peuvent cependant pas être réaffectées. Si vous devez pointer d’abord sur un objet, puis sur un autre, vous devez utiliser un pointeur. Les références ne peuvent pas être nulles. Par conséquent, s'il existe une chance que l'objet en question soit nul, vous ne devez pas utiliser de référence. Vous devez utiliser un pointeur. Si vous voulez gérer vous-même la manipulation d’objet, c’est-à-dire si vous souhaitez allouer de la mémoire à un objet situé sur le tas plutôt que sur la pile, vous devez utiliser le pointeur.

int *pInt = new int; // allocates *pInt on the Heap
0
Shashikant Mitkari

En général, une variable membre ne devrait jamais être une référence car cela ne sert à rien. Cela rend la classe non assignable si vous ne fournissez pas d'opérateur d'affectation. De plus, une fois que vous avez défini la référence de membre pour faire référence à un objet, il n'est pas possible de changer ce membre pour désigner un autre objet. L'utilisation la plus appropriée d'une référence est l'utilisation d'un paramètre de fonction qui permet de passer par référence.

0
fatma.ekici