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.
add_one(a);
n'est pas clair quia
va être modifié? Il dit juste dans le code: ajoutez-en un .addOneTo(...)
. Si ce n'est pas ce que vous voulez faire, regardez simplement la déclaration.Réponses:
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:
nullptr
comme argument, vous allez dans un pays à comportement indéfini;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
nullptr
comme argument et c'est bien pour la fonction. C'est une sorte de contrat entre l'utilisateur et l'implémentation.la source
add_one(a)
pas retourner le résultat, plutôt que de le définir par référence?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.NULL
etnullptr
, 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 toujoursboost::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 etreturn nullptr;
d'indiquer une valeur manquante ... Pourquoi importer tout le Boost?if
est obsolète et on devrait utiliser à lawhile() { 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?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.
la source
add_one
est 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 .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.De C ++ FAQ Lite -
la source
C with class
(qui n'est pas C ++).Ma règle d'or est la suivante:
&
)const
si c'est un paramètre entrant)const T&
).int ¤t = 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.
la source
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,
pourrait être plus clair que
puisque vous savez que la valeur de a va probablement changer. Par contre, la signature de la fonction
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
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.
la source
Comme d'autres ont déjà répondu: utilisez toujours des références, sauf si la variable étant
NULL
/nullptr
est vraiment un état valide.Le point de vue de John Carmack sur le sujet est similaire:
http://www.altdevblogaday.com/2011/12/24/static-code-analysis/
Modifier 2012-03-13
L'utilisateur Bret Kuhns remarque à juste titre:
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_ptr
etstd::shared_ptr
peuvent ê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).
la source
shared_ptr
etunique_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é.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.
la source
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
const
références. Pour les types non simples, passer unconst 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:
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.
la source
Copié du wiki -
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.
la source
Points à garder à l'esprit:
Les pointeurs peuvent l'être
NULL
, les références ne peuvent pas l'êtreNULL
.Les références sont plus faciles à utiliser,
const
peuvent ê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.Pointeur utilisé avec un
*
tandis que les références utilisées avec un&
.Utilisez des pointeurs lorsque l'opération arithmétique du pointeur est requise.
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
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.
la source
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.
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.Mais alors les provisoires viennent détruire votre monde heureux.
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.
Tenant compte de ces règles, mes règles actuelles sont les suivantes.
la source
const SimCard & m_card;
est juste un code mal écrit.const SimCard & m_card
soit 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.const SimCard & m_card
. Si vous voulez être efficace avec les temporaires, ajoutez unexplicit RefPhone(const SimCard&& card)
constructeur.Voici quelques directives.
Une fonction utilise des données transmises sans les modifier:
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.
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.
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.
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.
la source
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
la source
Votre exemple correctement écrit devrait ressembler à
C'est pourquoi les références sont préférables si possible ...
la source
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.
la source
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.
la source
&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.