web-dev-qa-db-fra.com

Référence aux variables de membre en tant que membres de classe

Sur mon lieu de travail, ce style est très utilisé: -

#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;
}

Y a-t-il un nom pour décrire cet idiome? Je suppose qu’il s’agit d’empêcher la surcharge d’un gros objet complexe?

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

54
Angus Comber

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

En UML, cela s'appelle l'agrégation. Il diffère de la composition en ce que l'objet membre n'est pas possédé 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 qu’il s’agit d’empêcher la surcharge d’un gros objet complexe?

Non, ce serait une très mauvaise raison d'utiliser ceci. La principale raison de l'agrégation est que l'objet contenu n'est pas la propriété de l'objet conteneur et que, par conséquent, leur durée de vie n'est pas liée. En particulier, la durée de vie de l'objet référencé doit survivre à celle de référence. Il a peut-être été créé beaucoup plus tôt et peut survivre au-delà de la durée de vie du conteneur. De plus, l'état de l'objet référencé n'est pas contrôlé par la classe, mais peut changer en externe. Si la référence n'est pas const, alors la classe peut changer l'état d'un objet qui vit en dehors de celui-ci.

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

C'est un outil de conception. Dans certains cas, ce sera une bonne idée, dans d'autres pas. 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 after 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 aussi efficace que les autres.

C'est ce qu'on appelle injection de dépendance via injection de constructeur : class A obtient la dépendance en tant qu'argument de son constructeur et enregistre la référence à la classe dépendante en tant que 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 les références aux objets temporaires:

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

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

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

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

private:
  const T &m_thing;
};

Quoi qu'il en soit si vous changez le constructeur:

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

private:
   const T &m_thing;
};

c'est à peu près garanti que vous n'aurez pas de pointeur sur un temporaire .

De plus, comme le constructeur utilise 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.


Les sujets un peu liés sont:

23
manlio

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

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

Je suppose que c'est pour éviter les frais importants liés à la copie d'un gros objet complexe?

Oui, ainsi que des scénarios dans lesquels vous souhaitez associer la durée de vie d’un objet à un autre.

Est-ce généralement une bonne pratique? Existe-t-il des pièges à cette approche?

Cela dépend de votre utilisation. Utiliser n'importe quelle langue est comme "choisir les chevaux pour les parcours" . Il est important de noter que chaque ( presque toutes les fonctionnalités de langage ) existe, car il est utile dans certains cas de figure.
Il convient de noter quelques points importants lors de l’utilisation de références en tant que membres du groupe:

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

Dans la plupart des cas pratiques (sauf si vous êtes vraiment préoccupé par l'utilisation importante de la mémoire en raison de la taille du membre), le simple fait de disposer d'une instance de membre suffit au lieu d'un pointeur ou d'un membre de référence. Cela vous évite de vous inquiéter des autres problèmes que les membres de référence/pointeur apportent, mais aux dépens d'une utilisation de mémoire supplémentaire.

Si vous devez utiliser un pointeur, veillez à utiliser un pointeur intelligent au lieu d'un pointeur brut. Cela vous rendrait la vie beaucoup plus facile avec des pointeurs.

17
Alok Save

C++ fournit un bon mécanisme pour gérer la durée de vie d'un objet à l'aide de constructions classe/structure. C'est l'une des meilleures fonctionnalités de C++ par rapport aux autres langages.

Lorsque vous avez des variables de membre exposées par le biais de ref ou d'un pointeur, cela enfreint en principe l'encapsulation. Cet idiome permet au consommateur de la classe de changer l'état d'un objet de A sans que celui-ci (A) en ait la connaissance ou le contrôle. Cela permet également au consommateur de conserver un repère/un pointeur sur l'état interne de A, au-delà de la durée de vie de l'objet de A. Il s'agit d'une mauvaise conception. Au lieu de cela, la classe pourrait être refactorisée pour contenir un ref/pointeur sur l'objet partagé (ne pas le posséder) et ceux-ci pourraient être définis à l'aide du constructeur (Mandate the rules time life). La classe de l'objet partagé peut être conçue pour prendre en charge le multithreading/concurrence, selon le cas.

1
Indy9000

Les références des membres sont généralement considérées comme mauvaises. Ils rendent la vie difficile par rapport aux pointeurs membres. Mais ce n'est pas particulièrement inhabituel, ni un idiome ou une chose nommée spéciale. C'est juste un aliasing.

1
Puppy