Quand utiliser les références par rapport aux pointeurs


381

Je comprends la syntaxe et la sémantique générale des pointeurs par rapport aux références, mais comment dois-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'un ou l'autre ( operator++nécessite un argument de référence), mais en général, je trouve que je préfère utiliser des pointeurs (et des pointeurs const) car la syntaxe est claire que les variables sont passées 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, donc pour les API et autres où la clarté est une grande préoccupation, 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 qu'en cas de besoin (par exemple operator++)? Y a-t-il des problèmes de performance avec l'un ou l'autre?

MODIFIER (MIS À JOUR):

En plus d'autoriser les valeurs NULL et de traiter les tableaux bruts, il semble que le choix se résume à des préférences personnelles. J'ai accepté la réponse ci-dessous qui fait référence au guide de style C ++ de Google , car ils présentent l'avis que "les références peuvent être déroutantes, car elles ont une syntaxe de valeur mais une sémantique de pointeur".

En raison du travail supplémentaire requis pour désinfecter les arguments de pointeur qui ne devraient pas être NULL (par exemple add_one(0), appellera la version du pointeur et s'arrêtera pendant l'exécution), il est logique, dans une perspective de maintenabilité, d'utiliser des références où un objet DOIT être présent, bien que ce soit une honte pour perdre la clarté syntaxique.


4
Il semble que vous ayez déjà pris la décision de savoir lequel utiliser quand. Personnellement, je préfère passer l'objet sur lequel j'agis, que je le modifie ou non. Si une fonction prend un pointeur, cela me dit qu'elle agit sur des pointeurs, c'est-à-dire en les utilisant comme itérateurs dans un tableau.
Benjamin Lindley

1
@Schnommus: Très bien, j'utilise principalement TextMate. Pourtant, je pense qu'il est préférable que la signification soit évidente d'un coup d'œil.
connec

4
Qu'est-ce qui add_one(a);n'est pas clair qui ava être modifié? Il dit juste dans le code: ajoutez-en un .
GManNickG

32
@connec: le guide de style Google C ++ n'est pas considéré comme un bon guide de style C ++. C'est un guide de style pour travailler avec l'ancienne base de code C ++ de Google (c'est-à-dire bon pour leurs trucs). Accepter une réponse basée sur cela n'aide personne. Juste en lisant vos commentaires et explications, vous êtes venu à cette question avec une opinion déjà définie et vous cherchez simplement d'autres personnes pour confirmer votre point de vue. En conséquence, vous basez la question et la réponse sur ce que vous voulez / attendez d'entendre.
Martin York

1
Ceci est simplement résolu en nommant la méthode addOneTo(...). Si ce n'est pas ce que vous voulez faire, regardez simplement la déclaration.
Stefan

Réponses:


297

Utilisez des références partout où vous le pouvez, des pointeurs partout où vous le devez.

Évitez les pointeurs jusqu'à ce que vous ne puissiez 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 toutes les autres constructions.

La règle d'or consiste donc à utiliser des pointeurs uniquement s'il n'y a pas d'autre choix.

Par exemple, le retour d'un pointeur sur un objet est une option valide lorsque la fonction peut retourner 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 vers 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 est inutile d'utiliser un pointeur comme argument car:

  1. si vous fournissez nullptrcomme argument, vous allez dans un pays à comportement indéfini;
  2. la version de l'attribut de référence ne permet pas (sans astuces faciles à repérer) 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, pas quelque chose qui pourrait être nul.

Si le comportement de la fonction devait fonctionner avec ou sans un objet donné, l'utilisation d'un pointeur comme attribut suggère que vous pouvez passer nullptrcomme argument et c'est bien pour la fonction. C'est une sorte de contrat entre l'utilisateur et l'implémentation.


49
Je ne suis pas sûr que les pointeurs rendent la lecture plus difficile? C'est un concept assez simple qui indique clairement quand quelque chose est susceptible d'être modifié. Si quelque chose que je dirais est plus difficile à lire lorsqu'il n'y a aucune indication de ce qui se passe, pourquoi ne add_one(a)pas retourner le résultat, plutôt que de le définir par référence?
connec

46
@connec: Si add_one(a)c'est déroutant, c'est parce qu'il est mal nommé. add_one(&a)aurait la même confusion, seulement maintenant vous pourriez incrémenter le pointeur et non l'objet. add_one_inplace(a)éviterait toute confusion.
Nicol Bolas

20
Un point, les références peuvent faire référence à une mémoire qui peut disparaître aussi facilement que les pointeurs. Ils ne sont donc pas nécessairement plus sûrs que les pointeurs. La persistance et la transmission de références peuvent être tout aussi dangereuses que des pointeurs.
Doug T.15

6
@Klaim, je voulais dire des pointeurs bruts. Je voulais dire que C ++ a des pointeurs, NULLet nullptr, et il les a pour une raison. Et ce n'est pas un conseil mûrement réfléchi ou même réaliste de dire que "n'utilisez jamais de pointeurs" et / ou "n'utilisez jamais NULL, utilisez toujours boost::optional". C'est juste fou. Ne vous méprenez pas, les pointeurs bruts sont moins souvent nécessaires en C ++ qu'en C, mais ils sont utiles, ils ne sont pas aussi "dangereux" que certaines personnes C ++ aiment à le dire (c'est aussi une exagération), et encore: quand il est plus facile d'utiliser simplement un pointeur et return nullptr;d'indiquer une valeur manquante ... Pourquoi importer tout le Boost?

5
@Klaim "utiliser NULL est une mauvaise pratique" - maintenant c'est tout simplement ridicule. Et ifest obsolète et on devrait utiliser à la while() { break; }place, non? De plus, ne vous inquiétez pas, j'ai vu et travaillé avec de grandes bases de code, et oui, si vous êtes négligent , la propriété est un problème. Pas si vous respectez les conventions, utilisez-les de manière cohérente et commentez et documentez votre code. Mais après tout, je devrais juste utiliser C parce que je suis trop stupide pour C ++, non?

62

Les performances sont exactement les mêmes, car les références sont implémentées en interne en tant que pointeurs. Vous n'avez donc pas à vous en préoccuper.

Il n'y a pas de convention généralement acceptée concernant l'utilisation des références et des pointeurs. Dans certains cas, vous devez renvoyer ou accepter des références (constructeur de copie, par exemple), mais à part cela, vous êtes libre de faire ce que vous voulez. 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 correcte.

Certaines conventions de codage (comme celle de Google ) prescrivent qu'il faut toujours utiliser des pointeurs ou des références const, car les références ont un peu de syntaxe peu claire: elles ont un comportement de référence mais une syntaxe de valeur.


10
Pour ajouter un peu à cela, le guide de style de Google indique que les paramètres d'entrée aux fonctions doivent être des références const et les sorties doivent être des pointeurs. J'aime cela parce que cela rend très clair lorsque vous lisez une signature de fonction ce qui est une entrée et ce qui est une sortie.
Dan

44
@Dan: Le guide de style Google est destiné à l'ancien code de Google et ne doit pas être utilisé pour le codage moderne. En fait, c'est un style de codage plutôt mauvais pour un nouveau projet.
GManNickG

13
@connec: Permettez-moi de le dire ainsi: null est une valeur de pointeur parfaitement valide . Partout où il y a un pointeur, je peux lui donner la valeur null. Ergo votre deuxième version add_oneest brisée : add_one(0); // passing a perfectly valid pointer value, Kaboom. Vous devez vérifier s'il est nul. Certaines personnes répliqueront: "eh bien je vais simplement documenter que ma fonction ne fonctionne pas avec null". C'est bien, mais vous allez à l'encontre du but de la question: si vous allez consulter la documentation pour voir si null est correct, vous verrez également la déclaration de fonction .
GManNickG

8
S'il s'agissait d'une référence, vous verriez que ce serait le cas. Une telle riposte manque cependant: les références imposent au niveau du langage qu'elles se réfèrent à un objet existant, et éventuellement pas nulles, alors que les pointeurs n'ont pas une telle restriction. Je pense qu'il est clair que l'application au niveau de la langue est plus puissante et moins sujette aux erreurs que l'application au niveau de la documentation. Certains essaieront de répondre à cela en disant: "Regardez, référence nulle:. int& i = *((int*)0);Ce n'est pas une cornue valide. Le problème dans le code précédent réside dans l'utilisation du pointeur, pas dans la référence . Les références ne sont jamais nulles, point.
GManNickG

12
Bonjour, j'ai vu un manque de juristes linguistiques dans les commentaires alors laissez-moi y remédier: les références sont généralement implémentées par des pointeurs mais la norme ne dit rien de tel. Une mise en œuvre utilisant un autre mécanisme serait une plainte à 100%.
Thomas Bonini

34

De C ++ FAQ Lite -

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

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

L'exception à ce qui précède est lorsque le paramètre ou la valeur de retour d'une fonction a besoin d'une référence "sentinelle" - une référence qui ne fait pas référence à un objet. Il est généralement préférable de retourner / prendre un pointeur et de donner au pointeur NULL cette signification spéciale (les références doivent toujours alias les objets, pas un pointeur NULL déréférencé).

Remarque: les anciens programmeurs de la ligne C n'aiment pas les références car ils fournissent une sémantique de référence qui n'est pas explicite dans le code de l'appelant. Après une certaine expérience en C ++, cependant, on se rend rapidement compte qu'il s'agit d'une forme de dissimulation d'informations, qui est un atout plutôt qu'un passif. Par exemple, les programmeurs doivent écrire du code dans la langue du problème plutôt que dans la langue de la machine.


1
Je suppose que vous pourriez faire valoir que si vous utilisez une API, vous devez être familier avec ce qu'elle fait et savoir si le paramètre passé est modifié ou non ... quelque chose à considérer, mais je me trouve d'accord avec les programmeurs C ( même si j'ai peu d'expérience en C). J'ajouterais cependant qu'une syntaxe plus claire est avantageuse pour les programmeurs ainsi que pour les machines.
connec

1
@connec: Assurez-vous que le programmeur C a correct pour leur langue. Mais ne faites pas l'erreur de traiter le C ++ comme C. C'est un langage complètement différent. Si vous traitez C ++ comme C, vous finissez par écrire ce qui est référencé de manière égale comme C with class(qui n'est pas C ++).
Martin York

15

Ma règle d'or est la suivante:

  • Utilisez des pointeurs pour les paramètres sortants ou entrants / sortants. On voit donc que la valeur va être modifiée. (Vous devez utiliser &)
  • Utilisez des pointeurs si le paramètre NULL est une valeur acceptable. (Assurez-vous que c'est constsi c'est un paramètre entrant)
  • Utilisez des références pour le paramètre entrant s'il ne peut pas être NULL et n'est pas un type primitif ( const T&).
  • Utilisez des pointeurs ou des pointeurs intelligents lors du retour 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.
  • Utilisez des références pour l'alias (par exemple. int &current = someArray[i])

Quel que soit celui que vous utilisez, n'oubliez pas de documenter vos fonctions et la signification de leurs paramètres s'ils ne sont pas évidents.


14

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

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

add_one(&a);

pourrait être plus clair que

add_one(a);

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

void add_one(int* const n);

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

foo(int* const a, int b);

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

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


Merci pour la démonstration claire de l'endroit où les deux solutions gagnent et perdent en clarté. J'étais au départ dans le camp des pointeurs, mais cela a beaucoup de sens.
Zach Beavon-Collin

12

Comme d'autres ont déjà répondu: utilisez toujours des références, sauf si la variable étant NULL/ nullptrest 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 ++, au moins dans notre code. La double utilisation d'une seule valeur comme indicateur et comme 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 n'est «vraiment» qu'un pointeur, elle a le contrat implicite d'être non-NULL. Effectuez des vérifications NULL lorsque les pointeurs sont transformés en références, vous pouvez ensuite ignorer le problème par la suite.

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

Modifier 2012-03-13

L'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 parfaitement fonctionner avec une combinaison de références, shared_ptr et unique_ptr.

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

Par exemple, les deux std::unique_ptret std::shared_ptrpeuvent être construits comme des pointeurs "vides" via leur constructeur par défaut:

... ce qui signifie que les utiliser sans vérifier qu'ils ne sont pas vides risque un crash, ce qui est exactement le sujet 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?"

La réponse de Jon à la question C ++ - en passant des 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 pas aussi clair qu'on le souhaiterait (je me préfère le " par référence "par défaut, mais je peux me tromper).


1
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 parfaitement fonctionner avec une combinaison de références,, shared_ptret unique_ptr. La sémantique de propriété et les conventions de paramètres d'entrée / sortie sont gérées par une combinaison de ces trois éléments et const'ness. Il n'y a presque pas besoin de pointeurs bruts en C ++, sauf lorsqu'il s'agit de code hérité et d'algorithmes très optimisés. Les zones où elles sont utilisées doivent être aussi encapsulées que possible et convertir tous les pointeurs bruts en l'équivalent "moderne" sémantiquement approprié.
Bret Kuhns

1
La plupart du temps, les pointeurs intelligents ne doivent pas être transmis, mais doivent être testés pour la nullité, puis leur objet contenu transmis par référence. La seule fois où vous devez réellement passer un pointeur intelligent, c'est lorsque vous transférez (unique_ptr) ou partagez (shared_ptr) la propriété avec un autre objet.
Luke Worth

@povman: Je suis entièrement d'accord: si la propriété ne fait pas partie de l'interface (et à moins qu'elle ne soit sur le point d'être modifiée, elle ne devrait pas l'être), alors nous ne devrions pas passer un pointeur intelligent en tant que paramètre (ou valeur de retour). La chose devient un peu plus compliquée lorsque la propriété fait partie de l'interface. Par exemple, le débat Sutter / Meyers sur la façon de passer un unique_ptr comme paramètre: par copie (Sutter) ou par référence de valeur r (Meyers)? Un anti-modèle repose sur le passage d'un pointeur vers un shared_ptr global, avec le risque que ce pointeur soit invalidé (la solution étant de copier le pointeur intelligent sur la pile)
paercebal

7

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

Si vous voulez faire référence à une variable déclarée statiquement dans la portée dans laquelle elle a été déclarée, utilisez une référence C ++, et ce sera parfaitement sûr. Il en va de même pour un pointeur intelligent déclaré statiquement. Le passage de paramètres par référence est un exemple de cette utilisation.

Si vous souhaitez faire référence à quelque chose d'une portée plus large que la portée dans laquelle il est déclaré, vous devez utiliser un pointeur intelligent compté par référence pour qu'il soit parfaitement sûr.

Vous pouvez vous référer à un élément d'une collection avec une référence pour plus de commodité syntaxique, 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é par référence.


5

Toute différence de performances serait si faible 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 celui des constré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 provoque la confusion même qui vous inquiète, car voir l'adresse prise et transmise à une fonction peut vous faire penser que la valeur a changé.

Quoi qu'il en soit, je suis fondamentalement 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. Il n'y a donc pas de confusion.

Les principaux avantages des références sont qu'elles contiennent toujours une valeur valide, sont plus propres que les pointeurs et 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.


4

Copié du wiki -

Une conséquence de cela est que dans de nombreuses implémentations, opérer sur une variable avec une durée de vie automatique ou statique via une référence, bien que syntaxiquement similaire à y accéder directement, peut impliquer des opérations de déréférencement cachées qui sont coûteuses. Les références sont une caractéristique syntaxiquement controversée de C ++ car elles masquent le niveau d'indirection d'un identifiant; c'est-à-dire que, contrairement au code C où les pointeurs se distinguent généralement syntaxiquement, 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 comme une variable locale ou globale ou s'il s'agit d'une référence (pointeur implicite) à un autre emplacement, surtout si le code mélange des références et des pointeurs. Cet aspect peut rendre le code C ++ mal écrit plus difficile à lire et à déboguer (voir Aliasing).

Je suis d'accord à 100% avec cela, et c'est pourquoi je pense que vous ne devez utiliser une référence que si vous avez une très bonne raison de le faire.


Je suis également d'accord dans une large mesure, mais j'arrive à penser que la perte de la protection intégrée contre les pointeurs NULL est un peu trop coûteuse pour des problèmes purement syntaxiques, d'autant plus que - bien que plus explicite - la syntaxe des pointeurs est assez moche en tous cas.
connec

Je suppose que la circonstance serait également un facteur important. Je pense qu'essayer d'utiliser des références lorsque la base de code actuelle utilise principalement des pointeurs serait une mauvaise idée. Si vous vous attendez à ce qu'ils soient des références, le fait que leur implicite soit moins importante peut-être ..
user606723

3

Points à garder à l'esprit:

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

  2. Les références sont plus faciles à utiliser, constpeuvent être utilisées comme référence lorsque nous ne voulons pas changer de valeur et avons juste besoin d'une référence dans une fonction.

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

  4. Utilisez des pointeurs lorsque l'opération arithmétique du pointeur est requise.

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

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

Aiguille : pour les tableaux, les listes de liens, les implémentations d'arborescence et l'arithmétique des pointeurs.

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


2

Il y a un problème avec la règle " utiliser les références dans la mesure du possible " et cela se produit si vous souhaitez conserver la référence pour une utilisation ultérieure. Pour illustrer cela avec un exemple, imaginez que vous avez des cours suivants.

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 d'avoir un paramètre dans le RefPhone(const SimCard & card)constructeur passé par une référence, car cela empêche de passer des pointeurs incorrects / nuls au constructeur. Il encourage en quelque sorte l'allocation de variables sur la pile et tire parti de 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 provisoires 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 passer des pointeurs invalides pour la possibilité de stocker des références à des objets détruits, ce qui a essentiellement le même effet.

modifier: Notez que je m'en suis tenu à la règle "Utilisez des références partout où vous le pouvez, des pointeurs partout où vous devez. Évitez les pointeurs jusqu'à ce que vous ne puissiez pas." à partir de la réponse la plus appréciée et acceptée (d'autres réponses le suggèrent également). Bien que cela doive être é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 les pointeurs, et ils peuvent apporter leurs propres menaces au code.


Il existe les différences suivantes entre les pointeurs et les références.

  1. Quand il s'agit de passer des variables, passer par référence ressemble à passer par valeur, mais a une sémantique de pointeur (agit comme un pointeur).
  2. La référence ne peut pas être directement initialisée à 0 (null).
  3. La référence (référence, objet non référencé) ne peut pas être modifiée (é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 ces règles, 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 si vous devez stocker le paramètre pour une utilisation ultérieure. Si 0 (null) est acceptable, j'ajoute le suffixe "_n" au paramètre, utilise un pointeur protégé (comme QPointer dans Qt) ou simplement le documente. 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 par conception et un gâchis de responsabilité).

3
Le problème avec votre exemple n'est pas que les références ne sont pas sûres, mais que vous comptez sur quelque chose hors de la portée de votre instance d'objet pour garder vos membres privés en vie. const SimCard & m_card;est juste un code mal écrit.
plamenko

@plamenko J'ai peur que vous ne compreniez pas le but de l'exemple. Que ce const SimCard & m_cardsoit correct ou non dépend du contexte. Le message dans ce post n'est pas que les références ne sont pas sûres (bien qu'elles puissent l'être si l'on essaie dur). Le message est que vous ne devez pas vous en tenir aveuglément au mantra «utilisez des références chaque fois que possible». L'exemple est le résultat d'une utilisation agressive de la doctrine de «l'utilisation des références chaque fois que possible». Cela devrait être clair.
doc

Il y a deux choses qui me dérangent avec votre réponse parce que je pense que cela peut induire en erreur quelqu'un qui essaie d'en savoir plus sur la question. 1. Le message est unidirectionnel et il est facile de se faire une idée que les références sont mauvaises. Vous n'avez fourni qu'un seul exemple de la façon de ne pas utiliser de références. 2. Vous n'avez pas été clair dans votre exemple sur ce qui ne va pas. Oui, temporaire obtiendra destroyet, mais ce n'était pas cette ligne qui était erronée, c'est l'implémentation de la classe.
plamenko

Vous ne devriez pratiquement jamais avoir de membres comme const SimCard & m_card. Si vous voulez être efficace avec les temporaires, ajoutez un explicit RefPhone(const SimCard&& card)constructeur.
plamenko

@plamenko si vous ne pouvez pas lire avec une certaine compréhension de base, vous avez un problème plus important que d'être trompé par mon message. Je ne sais pas comment je pourrais être plus clair. Regardez la première phrase. Il y a un problème avec le mantra "utiliser des références chaque fois que possible"! Où dans mon article vous avez trouvé une déclaration selon laquelle les références sont mauvaises? À la fin de mon article, vous avez écrit où utiliser les références, alors comment en êtes-vous arrivé à de telles conclusions? Ce n'est pas une réponse directe à la question?
doc

1

Voici quelques directives.

Une fonction utilise des données transmises 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é cette fonctionnalité.

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

1.Si l'objet de données est un type de données intégré, utilisez un pointeur. Si vous repérez du code comme fixit (& x), où x est un entier, il est assez clair que cette fonction a l'intention 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 lignes directrices et il peut 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.


0

Les références sont plus propres et plus faciles à utiliser, et elles cachent mieux les informations. Cependant, les références ne peuvent pas être réaffectées. Si vous devez pointer d'abord vers un objet puis vers un autre, vous devez utiliser un pointeur. Les références ne peuvent pas être nulles, donc 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 souhaitez gérer vous-même la manipulation d'objets, c'est-à-dire si vous souhaitez allouer de l'espace mémoire à un objet sur le tas plutôt que sur la pile, vous devez utiliser le pointeur

int *pInt = new int; // allocates *pInt on the Heap

0

Votre exemple correctement écrit devrait ressembler à

void add_one(int& n) { n += 1; }
void add_one(int* const n)
{
  if (n)
    *n += 1;
}

C'est pourquoi les références sont préférables si possible ...


-1

Je mets juste mon sou. Je viens de faire un test. Un sneeky à cela. Je laisse juste g ++ créer les fichiers d'assemblage du même mini-programme à l'aide de pointeurs par rapport à l'utilisation de références. En regardant la sortie, ils sont exactement les mêmes. Autre que la symbolisation. Donc, en regardant les performances (dans un exemple simple), il n'y a pas de problème.

Maintenant sur le sujet des pointeurs vs références. IMHO Je pense que la clarté est avant tout. Dès que je lis un comportement implicite, mes orteils commencent à s'enrouler. Je suis d'accord que c'est un comportement implicite agréable qu'une référence ne peut pas être NULL.

Déréférencer un pointeur NULL n'est pas le problème. il plantera votre application et 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 provoquant un comportement indéfini sans 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, que l'interface (qui doit être clairement documentée, voir conception par contrat, Bertrand Meyer) définit le résultat des paramètres d'une fonction. Maintenant, en prenant tout cela en considération, mes préférences vont à l'utilisation de références partout / chaque fois que possible.


-2

Pour les pointeurs, vous en avez besoin pour pointer vers quelque chose, donc les pointeurs coûtent de l'espace mémoire.

Par exemple, une fonction qui prend un pointeur entier ne prendra pas la variable entière. Vous devrez donc créer un pointeur pour que le premier passe à la fonction.

Quant à une référence, elle ne coûtera pas de mémoire. Vous avez une variable entière et vous pouvez la passer comme variable de référence. C'est ça. Vous n'avez pas besoin de créer une variable de référence spécialement pour elle.


4
Nan. Une fonction qui prend un pointeur ne nécessite pas d'allocation d'une variable de pointeur: vous pouvez passer un temporaire &address. Une référence coûtera certainement de la mémoire si elle est membre d'un objet, et en plus, tous les compilateurs existants implémentent réellement des références en tant qu'adresses, donc vous ne sauvegardez rien en termes de passage de paramètres ou de déréférencement non plus.
underscore_d
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.