web-dev-qa-db-fra.com

Que doit renvoyer un getter C++?

Quelle est la meilleure pratique pour une méthode getter C++ supposée renvoyer un type non trivial, mais un membre de type classe ou struct.

  1. Retour par valeur, telle que: MyType MyClass::getMyType() { return mMyType; } 
  2. Retour par référence constante: const MyType& MyClass::getMyType() { return mMyType; } 
  3. Retour par adresse: MyType* MyClass::getMyType() { return &mMyType; } 

class MyType { /* ... */ };

class MyClass
{
  private:
     MyType mMyType;
}

Je m'inquiète spécifiquement des utilisations suivantes de cette méthode. Pourriez-vous préciser en détail comment cela pourrait affecter la copie de l'objet et le danger de suspendre les références et les pointeurs décousus si function() souhaite le sauvegarder pour une utilisation ultérieure.

MyType* savedPointer;

SomeType function(MyType* pointer) { savedPointer = pointer; };

une. valable pour 1. et 2.

{
  MyType t = myClass.getMyType();
  function(&t);
}

// is savedPointer still valid here?

b. valable pour 1. et 2.

{
  const MyType& t = myClass.getMyType();
  function(&t);
}

// is savedPointer still valid here?

c. valable pour 1. et 2.

{
  MyType& t = myClass.getMyType();
  function(&t);
}

// is savedPointer still valid here?

ré. valable pour 3.

{
  MyType* t = myClass.getMyType();
  function(t);
}

// is savedPointer still valid here?

myClass est un objet de type MyClass.

35
Ferenc Deak

Vous pouvez fournir à la fois les versions const et non const:

MyType       & MyClass::getMyType()       { return mMyType; }
MyType const & MyClass::getMyType() const { return mMyType; }

Je ne fournirais pas de version de pointeur, car cela impliquerait que la valeur de retour puisse être le pointeur null, ce qui ne peut jamais être le cas en l'occurrence.

Le vrai problème, cependant, est que vous donnez à l'appelant un accès direct à l'objet interne. Si tel est votre intention, vous pouvez également rendre public le membre de données. Si ce n'est pas le cas, vous devrez alors travailler plus fort pour cacher l'objet.

Une option consiste à conserver l'accesseur MyType const &, mais à fournir un moyen plus indirect de modifier l'objet interne (setMyType(…) ou quelque chose de plus adapté à la sémantique que vous essayez d'exprimer au niveau de la classe contenue).

17
Marcelo Cantos

En général, vous devriez préférer retourner par valeur, à moins que Ne souhaite explicitement pas garantir que la référence désignera Un membre (qui expose une partie de votre implémentation, mais est souhaitable dans des cas comme std::vector<>::operator[]). Renvoyer Une référence empêche les modifications ultérieures de la classe, car cela signifie que vous ne pouvez pas renvoyer de valeur calculée. (Cela est particulièrement important pour Si la classe est conçue pour être une classe de base, car Renvoyer une référence crée cette restriction pour toutes les classes Dérivées.)

Le seul moment où vous devez retourner par pointeur est si une recherche ou Quelque chose est impliqué, ce qui peut renvoyer à devoir renvoyer Un pointeur nul. 

Renvoyer une référence à const peut être une optimisation valide si Le profileur indique ici des problèmes de performances et que l'appelsite peut également traiter une référence de const (aucune modification de La valeur renvoyée, aucun problème avec durée de vie de l'objet). Cela doit être mis en balance avec les contraintes supplémentaires qui pèsent sur la mise en œuvre de , Bien sûr, mais dans certains cas, cela est justifié.

13
James Kanze

Je retournerais toujours une référence const. Si vous devez modifier la valeur renvoyée, utilisez simplement une fonction de définition. 

3
Javier Castellanos

Retour par valeur, tel que: MyType MyClass::getMyType() { return mMyType; } devrait être évité car vous allez copier le contenu de votre objet. Je ne vois pas le gain que vous pourriez avoir mais je vois les inconvénients sur les performances.

Retour par référence const: const MyType& MyClass::getMyType() { return mMyType; } est plus généralement utilisé de cette façon:

const MyType& MyClass::getMyType() const { return mMyType; }
MyType& MyClass::getMyType() { return mMyType; }

Toujours fournir la version const. La version non-const est facultative car cela signifie que quelqu'un peut modifier vos données. C'est ce que je vous encourage à utiliser.

Retour par adresse: MyType* MyClass::getMyType() { return &mMyType; } est principalement utilisé lorsque les données y sont facultatives. Il doit souvent être vérifié avant d'être utilisé.

Maintenant, votre cas d'utilisation, je vous conseillerais vivement de ne pas garder un pointeur sauf pour une portée. Je peux souvent conduire à des problèmes de propriété. Si vous devez le faire, jetez un oeil à shared_ptr .

Pour vos exemples, il y a deux cas:

une. savedPointer ne sera plus valide après l'accolade de fermeture.

b, c et d. savedPointer est valide après l'accolade de fermeture, mais attention, il ne faut pas survivre à myClass.

1
Johan

a) MyType t = créera une copie de l'objet pour 1 et 2. Le pointeur enregistré ne sera pas valide une fois que t sera hors de portée.

b) Le pointeur enregistré sera valide pour le cas renvoyant une référence, mais non valide pour le cas renvoyant un objet. Pour la référence, la durée de vie du pointeur serait la même que celle de myClass. Bien sûr, une référence constante serait une const t * pas une t * et échouerait donc dans l'appel à function(MyType*).

c) Identique à b, bien que le code ne soit pas valide pour 2 car vous ne pouvez pas convertir un const MyType& en un MyType&. En règle générale, ce serait une mauvaise pratique et la forme const serait plus acceptable.

d) savedPointer aura la même durée de vie que myClass.

Je préférerais généralement renvoyer une référence ou une référence const en fonction de ce que vous vous attendez à pouvoir faire avec la valeur de retour. Si vous renvoyez une référence (non-const), vous pouvez effectuer des opérations telles que: myClass.getMyType() = ..., tandis que si vous renvoyez une référence const, l'objet est en lecture seule.

0
Charlie