103 votes

Référence des variables membres comme membres de la classe

Sur mon lieu de travail, je vois ce style utilisé de manière intensive:-.

#include <iostream>

using namespace std;

class A
{
public:
   A(int& thing) : m_thing(thing) {}
   void printit() { cout << m_thing << endl; }

protected:
   const int& m_thing; //usually would be more complex object
};

int main(int argc, char* argv[])
{
   int myint = 5;
   A myA(myint);
   myA.printit();
   return 0;
}

Existe-t-il un nom pour décrire cet idiome ? Je suppose qu'il s'agit d'éviter la surcharge éventuelle de la copie d'un gros objet complexe ?

Est-ce une bonne pratique en général ? Y a-t-il des pièges à cette approche ?

141voto

Existe-t-il un nom pour décrire cet idiome ?

En UML, cela s'appelle l'agrégation. Elle diffère de la composition dans la mesure où l'objet membre n'est pas appartenant à par la classe de référence. En C++, vous pouvez implémenter l'agrégation de deux manières différentes, par le biais de références ou de pointeurs.

Je suppose que c'est pour éviter l'éventuelle surcharge de la copie d'un gros objet complexe ?

Non, ce serait une très mauvaise raison d'utiliser ça. La principale raison de l'agrégation est que l'objet contenu n'appartient pas à l'objet contenant et que leurs durées de vie ne sont donc pas liées. En particulier, la durée de vie de l'objet référencé doit dépasser celle de l'objet référent. Il peut avoir été créé bien plus tôt et peut vivre au-delà de la fin de vie du conteneur. En outre, l'état de l'objet référencé n'est pas contrôlé par la classe, mais peut changer de manière externe. Si la référence n'est pas const alors la classe peut changer l'état d'un objet qui vit en dehors d'elle.

Est-ce une bonne pratique en général ? Y a-t-il des pièges à cette approche ?

Il s'agit d'un outil de conception. Dans certains cas, ce sera une bonne idée, dans d'autres non. Le piège le plus courant est que la durée de vie de l'objet contenant la référence ne doit jamais dépasser la durée de vie de l'objet référencé. Si l'objet englobant utilise la référence après l'objet référencé a été détruit, vous aurez un comportement indéfini. En général, il est préférable de préférer la composition à l'agrégation, mais si vous en avez besoin, c'est un outil comme un autre.

47voto

manlio Points 3407

Cela s'appelle injection de dépendances via l'injection de constructeurs : classe A reçoit la dépendance comme argument de son constructeur et enregistre la référence à la classe dépendante comme variable privée.

Il y a une introduction intéressante sur wikipedia .

Pour const-correctness J'écrirais :

using T = int;

class A
{
public:
  A(const T &thing) : m_thing(thing) {}
  // ...

private:
   const T &m_thing;
};

mais un problème avec cette classe est qu'elle accepte des références à des objets temporaires :

T t;
A a1{t};    // this is ok, but...

A a2{T()};  // ... this is BAD.

Il est préférable de l'ajouter (nécessite C++11 au moins) :

class A
{
public:
  A(const T &thing) : m_thing(thing) {}
  A(const T &&) = delete;  // prevents rvalue binding
  // ...

private:
  const T &m_thing;
};

De toute façon, si vous changez le constructeur :

~~

class A
{
public:
  A(const T *thing) : m_thing(*thing) { assert(thing); }
  // ...

private:
   const T &m_thing;
};

il est presque garanti que vous n'aurez pas de pointeur vers un fichier temporaire. .

~~

De plus, puisque le constructeur prend un pointeur, il est plus clair pour les utilisateurs de A qu'ils doivent faire attention à la durée de vie de l'objet qu'ils passent.


Des sujets quelque peu apparentés :

26voto

Alok Save Points 115848

Existe-t-il un nom pour décrire cet idiome ?

Il n'y a pas de nom pour cet usage, il est simplement connu sous le nom de "Référence en tant que membre de la classe" .

Je suppose que c'est pour éviter l'éventuelle surcharge de la copie d'un gros objet complexe ?

Oui et aussi des scénarios où vous voulez associer la durée de vie d'un objet à un autre objet.

Est-ce une bonne pratique en général ? Y a-t-il des pièges à cette approche ?

Cela dépend de votre utilisation. Utiliser n'importe quelle fonctionnalité du langage, c'est comme "choisir des cours pour des cours" . Il est important de noter que chaque ( presque tous ) La caractéristique de la langue existe parce qu'elle est utile dans un certain scénario.
Il y a quelques points importants à noter lors de l'utilisation de références comme membres de classe :

  • Vous devez vous assurer que l'objet référencé est garanti d'exister jusqu'à ce que votre objet de classe existe.
  • Vous devez initialiser le membre dans la liste d'initialisation du membre du constructeur. Vous ne pouvez pas avoir un Initialisation paresseuse ce qui pourrait être possible dans le cas d'un membre pointeur.
  • Le compilateur ne générera pas l'affectation de copie operator=() et vous devrez en fournir un vous-même. Il est fastidieux de déterminer quelle action votre = l'opérateur doit prendre dans un tel cas. Donc, en gros, votre classe devient non-assignable .
  • Les références ne peuvent pas être NULL ou de faire référence à tout autre objet. Si vous avez besoin de changer de place, ce n'est pas possible avec une référence comme dans le cas d'un pointeur.

Dans la plupart des cas (à moins que vous ne soyez vraiment préoccupé par une utilisation élevée de la mémoire en raison de la taille du membre), le fait d'avoir une instance de membre, au lieu d'un pointeur ou d'un membre de référence, devrait suffire. Cela vous évite de vous soucier des autres problèmes que les membres de référence/pointeur entraînent, mais au prix d'une utilisation supplémentaire de la mémoire.

Si vous devez utiliser un pointeur, veillez à utiliser un pointeur intelligent plutôt qu'un pointeur brut. Cela vous rendra la vie beaucoup plus facile avec les pointeurs.

1voto

Kip9000 Points 4462

Le C++ fournit un bon mécanisme pour gérer la durée de vie d'un objet par le biais des constructions classe/structure. C'est l'une des meilleures caractéristiques du C++ par rapport aux autres langages.

Lorsque vous avez des variables membres exposées par le biais de ref ou de pointeur, cela viole l'encapsulation en principe. Cet idiome permet au consommateur de la classe de changer l'état d'un objet de A sans qu'il (A) en ait connaissance ou le contrôle. Il permet également au consommateur de conserver un ref/pointeur vers l'état interne de A, au-delà de la durée de vie de l'objet de A. C'est une mauvaise conception. Au lieu de cela, la classe pourrait être remaniée pour contenir un réf/pointeur vers l'objet partagé (et non le posséder) et ceux-ci pourraient être définis en utilisant le constructeur (Mandate the life time rules). La classe de l'objet partagé peut être conçue pour supporter le multithreading/concurrence selon le cas.

-3voto

Puppy Points 90818

Les références aux membres sont généralement considérées comme mauvaises. Elles rendent la vie difficile par rapport aux pointeurs de membres. Mais ce n'est pas particulièrement inhabituel, et il ne s'agit pas non plus d'un idiome ou d'un truc spécial. Il s'agit simplement d'un alias.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X