web-dev-qa-db-fra.com

Le renvoi de références de variables membres est-il une mauvaise pratique?

On dit que ce qui suit est mieux que d'avoir le premier/second comme membres publics. Je pense que c'est presque aussi mauvais. Si vous donnez un moyen d'accéder à une variable privée en dehors de la classe, quel est l'intérêt? Les fonctions ne devraient-elles pas être

T First(); void(or T) First(const T&)

Échantillon:

// Example 17-3(b): Proper encapsulation, initially with inline accessors. Later
// in life, these might grow into nontrivial functions if needed; if not, then not.
//
template<class T, class U>
class Couple {
  Couple()           : deleted_(false) { }
  T& First()         { return first_; }
  U& Second()        { return second_; }
  void MarkDeleted() { deleted_ = true; }
  bool IsDeleted()   { return deleted_; }

private:
 T first_;
 U second_;
 bool deleted_;
};
46
user34537

Il y a plusieurs raisons pour lesquelles le renvoi de références (ou pointeurs) aux internes d'une classe est mauvais. En commençant par (ce que je considère être) le plus important:

  1. Encapsulation est violé: vous divulguez un détail d'implémentation, ce qui signifie que vous ne pouvez plus modifier les internes de votre classe comme vous le souhaitez. Si vous avez décidé de ne pas stocker first_ par exemple, mais pour le calculer à la volée, comment voulez-vous lui renvoyer une référence? Vous ne pouvez pas, vous êtes donc coincé.

  2. Invariant ne sont plus viables (en cas de référence non constante): n'importe qui peut accéder et modifier à volonté l'attribut auquel il est fait référence, vous ne pouvez donc pas "surveiller" ses changements. Cela signifie que vous ne pouvez pas conserver un invariant dont cet attribut fait partie. Essentiellement, votre classe se transforme en goutte.

  3. Durée de vie des problèmes surgissent: il est facile de conserver une référence ou un pointeur sur l'attribut après que l'objet d'origine auquel ils appartiennent a cessé d'exister. Il s'agit bien sûr d'un comportement indéfini. La plupart des compilateurs tenteront de vous avertir de garder des références aux objets sur la pile, par exemple, mais je ne connais aucun compilateur qui ait réussi à produire de tels avertissements pour les références renvoyées par des fonctions ou des méthodes: vous êtes seul.

En tant que tel, il est généralement préférable de ne pas donner de références ou de pointeurs aux attributs. Pas même les const!

Pour les petites valeurs, il suffit généralement de les passer par copie (à la fois in et out), surtout maintenant avec la sémantique de déplacement (en cours de route).

Pour des valeurs plus grandes, cela dépend vraiment de la situation, parfois un proxy peut atténuer vos problèmes.

Enfin, notez que pour certaines classes, avoir des membres publics n'est pas si mal. Quel serait l'intérêt d'encapsuler les membres d'un pair? Lorsque vous vous retrouvez en train d'écrire une classe qui n'est rien de plus qu'une collection d'attributs (pas du tout invariant), alors au lieu d'obtenir tout OO sur nous et d'écrire une paire getter/setter pour chacun d'eux) , pensez plutôt à les rendre publics.

53
Matthieu M.

Si template types T et U sont de grandes structures alors le retour par valeur est coûteux. Cependant, vous avez raison de dire que le renvoi par référence équivaut à donner accès à une variable private. Pour résoudre les deux problèmes, faites-les const références:

const T& First()  const  { return first_; }
const U& Second() const  { return second_; }

P.S. Aussi, c'est une mauvaise pratique de garder les variables non initialisées à l'intérieur du constructeur, quand il n'y a pas de méthode de définition. Il semble que dans le code d'origine, First() et Second() sont des wrappers sur first_ Et second_ Qui étaient destinés à la lecture/écriture à la fois.

25
iammilind

La réponse dépend de ce que l'on essaie de faire. Le renvoi de références est un moyen pratique de faciliter la mutation des structures de données. Un bon exemple est la carte stl. Il renvoie une référence à l'élément, c'est-à-dire.

std::map<int,std::string> a;
a[1] = 1;

rien pour vous empêcher de faire

auto & aref = a[1];

Est-ce nécessairement une mauvaise pratique? Je ne pense pas. Je dirais que si vous pouvez vous en passer, faites-le. Si cela rend la vie plus pratique et efficace, utilisez-la et soyez conscient de ce que vous faites.

7
Ramesh Kadambi