web-dev-qa-db-fra.com

Devrais-je préférer les pointeurs ou les références dans les données des membres?

Voici un exemple simplifié pour illustrer la question:

class A {};

class B
{
    B(A& a) : a(a) {}
    A& a;
};

class C
{
    C() : b(a) {} 
    A a;
    B b; 
};

Donc B est responsable de la mise à jour d’une partie de C. J’ai analysé le code avec lint et a parlé du membre de référence: lint # 1725 . Il est question ici de prendre soin de la copie et des assignations par défaut qui sont assez justes , mais la copie et l’affectation par défaut sont également mauvaises avec les pointeurs, il ya donc peu d’avantage là-bas.

J'essaie toujours d'utiliser des références dans la mesure du possible car des pointeurs nus introduisent de manière incertaine l'identité du responsable de la suppression de ce pointeur. Je préfère incorporer des objets par valeur, mais si j'ai besoin d'un pointeur, j'utilise auto_ptr dans les données membres de la classe qui possède le pointeur et je passe l'objet comme référence.

En règle générale, je n’utiliserais un pointeur dans les données de membre que si le pointeur pouvait être nul ou pouvait changer. Existe-t-il d'autres raisons de préférer les pointeurs aux références pour les membres de données?

Est-il vrai de dire qu'un objet contenant une référence ne devrait pas être assignable, car une référence ne devrait pas être changée une fois initialisée?

112
markh44

Évitez les membres de référence, car ils limitent ce que l'implémentation d'une classe peut faire (y compris, comme vous le dites, empêcher l'implémentation d'un opérateur d'affectation) et n'apportent aucun avantage à ce que la classe peut fournir.

Exemple de problèmes:

  • vous êtes obligé d'initialiser la référence dans la liste d'initialisateurs de chaque constructeur: il n'y a aucun moyen de factoriser cette initialisation dans une autre fonction (jusqu'à ce que C++ 0x, de toute façon edit: C++ a maintenant délégation des constructeurs )
  • la référence ne peut pas être rebondie ou être nulle. Cela peut être un avantage, mais si le code doit être modifié pour permettre la ré-association ou que le membre soit nul, toutes les utilisations du membre doivent être modifiées.
  • contrairement aux membres de pointeur, les références ne peuvent pas facilement être remplacées par des pointeurs intelligents ou des itérateurs, car le refactoring peut nécessiter
  • Chaque fois qu'une référence est utilisée, elle ressemble à un type de valeur (opérateur ., etc.), mais se comporte comme un pointeur (peut être suspendue) - par ex. Google Style Guide le décourage
42
James Hopkin

Ma propre règle de base:

  • Utilisez un membre de référence lorsque vous souhaitez que la vie de votre objet soit dépendante de la vie d'autres objets : il s'agit d'un moyen explicite de dire que vous ne permettez pas à l'objet de rester en vie sans une instance valide d'une autre classe - en raison de l'absence d'affectation et de l'obligation d'obtenir l'initialisation des références via le constructeur. C'est un bon moyen de concevoir votre classe sans supposer que son instance est membre ou non d'une autre classe. Vous supposez seulement que leur vie est directement liée à d'autres instances. Cela vous permet de changer plus tard comment vous utilisez votre instance de classe (avec new, en tant qu'instance locale, en tant que membre de la classe, générée par un pool de mémoire dans un gestionnaire, etc.)
  • Utilisez le pointeur dans les autres cas : Si vous souhaitez que le membre soit modifié ultérieurement, utilisez un pointeur ou un pointeur const pour vous assurer de ne lire que l'occurrence désignée. Si ce type est censé être copié, vous ne pouvez pas utiliser les références de toute façon. Parfois, vous devez également initialiser le membre après un appel de fonction spéciale (init () par exemple) et vous n'avez alors d'autre choix que d'utiliser un pointeur. MAIS: utilisez des assertions dans toutes vos fonctions de membre pour détecter rapidement le mauvais état du pointeur!  
  • Dans les cas où vous souhaitez que la durée de vie d'un objet dépende de la durée de vie d'un objet externe et que vous ayez également besoin de ce type pour pouvoir être copié, utilisez des membres de pointeur mais un argument de référence dans le constructeur Ainsi, vous indiquez lors de la construction de cet objet dépend de la durée de vie de l'argument MAIS l'implémentation utilise toujours des pointeurs pour pouvoir être copiés. Tant que ces membres ne sont modifiés que par copie et que votre type n'a pas de constructeur par défaut, le type doit remplir les deux objectifs.
121
Klaim

Les objets devraient rarement permettre d’assigner des tâches et d’autres choses comme la comparaison. Si vous envisagez un modèle d'entreprise avec des objets tels que «Département», «Employé», «Directeur», il est difficile d'imaginer un cas où un employé sera affecté à un autre.

Pour les objets métier, il est donc très utile de décrire les relations un à un et un à plusieurs comme des références et non des pointeurs.

Et il est probablement correct de décrire la relation un ou zéro comme un pointeur.

Donc, non, nous ne pouvons pas affecter ce facteur.
Beaucoup de programmeurs s'habituent simplement aux pointeurs et c'est pourquoi ils trouveront n'importe quel argument pour éviter l'utilisation de la référence.

Avoir un pointeur en tant que membre vous obligera, vous ou un membre de votre équipe, à vérifier le pointeur encore et encore avant utilisation, avec un commentaire "juste au cas où". Si un pointeur peut être égal à zéro, le pointeur est probablement utilisé comme une sorte d’indicateur, ce qui est mauvais, car chaque objet doit jouer son propre rôle.

32
Mykola Golubyev
11
ebo

Dans quelques cas importants, l’assignabilité n’est tout simplement pas nécessaire. Ce sont souvent des wrappers d'algorithmes légers qui facilitent les calculs sans quitter la portée. De tels objets sont les premiers candidats pour les membres de référence car vous pouvez être sûr qu'ils ont toujours une référence valide / et qu'ils n'ont jamais besoin d'être copiés.

Dans de tels cas, veillez à rendre l'opérateur d'affectation (et souvent aussi le constructeur de copie) non utilisable (en héritant de boost::noncopyable ou en les déclarant privés).

Cependant, comme les utilisateurs pts ont déjà commenté, il n'en va pas de même pour la plupart des autres objets. Ici, utiliser des membres de référence peut être un problème énorme et devrait généralement être évité.

6
Konrad Rudolph

Comme tout le monde semble donner des règles générales, je vais en proposer deux:

  • Jamais, jamais utiliser des références d'utilisation en tant que membres de classe. Je ne l'ai jamais fait dans mon propre code (sauf pour me prouver que j'avais raison avec cette règle) et je ne peux pas imaginer un cas où je le ferais. La sémantique est trop déroutante et ce n’est vraiment pas la raison pour laquelle les références ont été conçues.

  • Toujours, toujours, utilisez des références lorsque vous passez des paramètres à des fonctions, sauf pour les types de base, ou lorsque l'algorithme nécessite une copie.

Ces règles sont simples et m'ont bien servi. Je laisse faire des règles sur l'utilisation de pointeurs intelligents (mais s'il vous plaît, pas auto_ptr) en tant que membres du groupe aux autres.

6
anon

Oui à: Est-il vrai de dire qu'un objet contenant une référence ne doit pas être assignable, car une référence ne doit pas être modifiée une fois initialisée?

Mes règles empiriques pour les membres de données:

  • ne jamais utiliser une référence, car cela empêche l'affectation
  • si votre classe est responsable de la suppression, utilisez scoped_ptr de boost (qui est plus sûr qu'un auto_ptr)
  • sinon, utilisez un pointeur ou un pointeur const
4
pts

En règle générale, je n’utiliserais un pointeur dans les données de membre que si le pointeur pouvait être nul ou pouvait changer. Existe-t-il d'autres raisons de préférer les pointeurs aux références pour les membres de données?

Oui. Lisibilité de votre code. Un pointeur indique plus clairement que le membre est une référence (ironiquement :)) et non un objet contenu, car lorsque vous l'utilisez, vous devez le dé-référencer. Je sais que certaines personnes pensent que c'est démodé, mais je pense toujours que cela évite simplement la confusion et les erreurs. 

2

Je déconseille aux membres des données de référence car vous ne savez jamais qui va dériver de votre classe et ce qu'ils voudront peut-être faire. Ils ne souhaitent peut-être pas utiliser l'objet référencé, mais en tant que référence, vous les avez obligés à fournir un objet valide . Je me suis suffisamment fait cela pour cesser d'utiliser des membres de données de référence.

2
StephenD

Si je comprends bien la question ...

Référence en tant que paramètre de fonction au lieu de pointeur: Comme vous l'avez souligné, un pointeur n'indique pas clairement à qui appartient le nettoyage/l'initialisation du pointeur. Préférez un point partagé lorsque vous souhaitez un pointeur, il fait partie de C++ 11 et un faible_ptr lorsque la validité des données n'est pas garantie pendant toute la durée de vie de la classe acceptant les données pointées. également une partie de C++ 11 . L'utilisation d'une référence en tant que paramètre de fonction garantit que la référence n'est pas nulle. Il faut subvertir les fonctionnalités du langage pour résoudre ce problème, et nous ne nous soucions pas des codeurs de canons mal fixés.

Référence une variable a membre: Comme ci-dessus en ce qui concerne la validité des données. Cela garantit que les données pointées, puis référencées, sont valides.

Le fait de déplacer la responsabilité de validité variable vers un point antérieur du code nettoie non seulement le code suivant (la classe A dans votre exemple), mais le précise également à l’utilisateur. 

Dans votre exemple, qui est un peu déroutant (j'essaierais vraiment de trouver une implémentation plus claire), le A utilisé par B est garanti pour la durée de vie du B, B étant un membre de A, une référence le renforce. et est (peut-être) plus clair.

Et juste au cas où (un capot peu probable car cela n’aurait aucun sens dans le contexte de vos codes), une autre alternative, utiliser un paramètre non référencé, non pointeur A, copierait A et rendrait le paradigme inutile - I vraiment ne pense pas que vous entendez cela comme une alternative.

En outre, cela garantit également que vous ne pouvez pas modifier les données référencées, où un pointeur peut être modifié. Un pointeur const ne fonctionnerait que si le pointeur référencé/data n'était pas modifiable.

Les pointeurs sont utiles s'il n'est pas garanti que le paramètre A pour B soit défini ou s'il peut être réaffecté. Et parfois, un pointeur faible est trop bavard pour la mise en œuvre, et bon nombre de personnes ne savent pas ce qu'est un faible, ou ne les aiment pas. 

Découpez cette réponse, s'il vous plaît :)

0
Kit10