94 votes

Comment faire passer std::unique_ptr ?

C'est ma première tentative d'utilisation de C++11. unique_ptr Je suis en train de remplacer un pointeur brut polymorphe dans un de mes projets, qui est la propriété d'une classe, mais qui circule assez fréquemment.

J'avais des fonctions comme :

bool func(BaseClass* ptr, int other_arg) {
  bool val;
  // plain ordinary function that does something...
  return val;
}

Mais j'ai vite compris que je ne pourrais pas passer à.. :

bool func(std::unique_ptr<BaseClass> ptr, int other_arg);

Parce que l'appelant devrait gérer la propriété du pointeur vers la fonction, ce que je ne veux pas faire. Alors, quelle est la meilleure solution à mon problème ?

J'ai pensé à passer le pointeur comme référence, comme ceci :

bool func(const std::unique_ptr<BaseClass>& ptr, int other_arg);

Mais je me sens très mal à l'aise en le faisant, d'abord parce qu'il ne semble pas instinctif de passer quelque chose déjà tapé comme _ptr comme référence, ce qui serait une référence d'une référence. Deuxièmement, parce que la signature de la fonction devient encore plus grande. Troisièmement, parce que dans le code généré, il faudrait deux indirections de pointeur consécutives pour atteindre ma variable.

108voto

R. Martinho Fernandes Points 96873

Si vous voulez que la fonction utilise le pointeur, passez une référence à celui-ci. Il n'y a aucune raison de lier la fonction pour qu'elle ne fonctionne qu'avec une sorte de pointeur intelligent :

bool func(BaseClass& base, int other_arg);

Et sur le site d'appel, utilisez operator* :

func(*some_unique_ptr, 42);

Alternativement, si le base est autorisé à être nul, gardez la signature telle quelle, et utilisez l'argument get() fonction membre :

bool func(BaseClass* base, int other_arg);
func(some_unique_ptr.get(), 42);

41voto

Victor Savu Points 562

L'avantage d'utiliser std::unique_ptr<T> (à part le fait de ne pas avoir à se rappeler d'appeler delete o delete[] explicitement) est qu'il garantit qu'un pointeur est soit nullptr ou il pointe vers une instance valide de l'objet (de base). J'y reviendrai après avoir répondu à votre question, mais le premier message est le suivant DO utiliser des pointeurs intelligents pour gérer la durée de vie des objets alloués dynamiquement.

Maintenant, votre problème est en fait comment l'utiliser avec votre ancien code .

Ma suggestion est que si vous ne voulez pas transférer ou partager la propriété, vous devriez toujours passer les références à l'objet. Déclarez votre fonction comme ceci (avec ou sans const qualificatifs, si nécessaire) :

bool func(BaseClass& ref, int other_arg) { ... }

Ensuite, l'appelant, qui a un std::shared_ptr<BaseClass> ptr traitera soit le nullptr ou il demandera bool func(...) pour calculer le résultat :

if (ptr) {
  result = func(*ptr, some_int);
} else {
  /* the object was, for some reason, either not created or destroyed */
}

Cela signifie que tout appelant doit promesse que la référence est valide et qu'elle continuera à l'être tout au long de l'exécution du corps de la fonction.


Voici les raisons pour lesquelles je suis convaincu que vous devriez le faire no passer des pointeurs bruts ou des références à des pointeurs intelligents.

Un pointeur brut n'est qu'une adresse mémoire. Il peut avoir l'une des 4 significations suivantes (au moins) :

  1. L'adresse d'un bloc de mémoire où se trouve l'objet souhaité. ( le bien )
  2. L'adresse 0x0 dont vous pouvez être certain qu'elle n'est pas déréférençable et pourrait avoir la sémantique de "rien" ou "aucun objet". ( le mauvais )
  3. L'adresse d'un bloc de mémoire qui est en dehors de l'espace adressable de votre processus (le déréférencer provoquera, on l'espère, le plantage de votre programme). ( le laid )
  4. L'adresse d'un bloc de mémoire qui peut être déréférencé mais qui ne contient pas ce que vous attendez. Peut-être que le pointeur a été accidentellement modifié et qu'il pointe maintenant vers une autre adresse accessible en écriture (d'une toute autre variable dans votre processus). Le fait d'écrire dans cet emplacement de mémoire va provoquer beaucoup d'amusement, à certains moments, pendant l'exécution, parce que le système d'exploitation ne se plaindra pas tant que vous serez autorisé à écrire à cet endroit. ( Zoinks ! )

L'utilisation correcte des pointeurs intelligents permet d'éviter les cas 3 et 4, plutôt effrayants, qui ne sont généralement pas détectables à la compilation et que vous ne rencontrez qu'à l'exécution, lorsque votre programme se plante ou fait des choses inattendues.

Passer des pointeurs intelligents en tant qu'arguments présente deux inconvénients : vous ne pouvez pas modifier l'adresse de l'utilisateur. const -de l pointu sans faire de copie (ce qui ajoute de la surcharge pour l'utilisateur). shared_ptr et n'est pas possible pour unique_ptr ), et il vous reste le deuxième ( nullptr ) signifiant.

J'ai marqué le deuxième cas comme ( le mauvais ) du point de vue de la conception. Il s'agit d'un argument plus subtil sur la responsabilité.

Imaginez ce que cela signifie lorsqu'une fonction reçoit un nullptr comme son paramètre. Il doit d'abord décider ce qu'il doit en faire : utiliser une valeur "magique" à la place de l'objet manquant ? changer complètement de comportement et calculer autre chose (qui ne nécessite pas l'objet) ? paniquer et lancer une exception ? De plus, que se passe-t-il lorsque la fonction prend 2, ou 3 ou même plus d'arguments par pointeur brut ? Elle doit vérifier chacun d'entre eux et adapter son comportement en conséquence. Cela ajoute un tout nouveau niveau en plus de la validation de l'entrée sans véritable raison.

L'appelant devrait être celui qui dispose de suffisamment d'informations contextuelles pour prendre ces décisions, ou, en d'autres termes, le mauvais est moins effrayant quand on en sait plus. La fonction, quant à elle, doit simplement accepter la promesse de l'appelant que la mémoire vers laquelle elle pointe est sûre et qu'elle peut être utilisée comme prévu. (Les références sont toujours des adresses de mémoire, mais représentent conceptuellement une promesse de validité).

29voto

Mikael Persson Points 7174

Je suis d'accord avec Martinho, mais je pense qu'il est important de souligner la sémantique de propriété d'une passe par référence. Je pense que la solution correcte est d'utiliser un simple pass-by-reference ici :

bool func(BaseClass& base, int other_arg);

La signification communément acceptée d'un pass-by-reference en C++ est comme si l'appelant de la fonction disait à la fonction "ici, tu peux emprunter cet objet, l'utiliser et le modifier (s'il n'est pas const), mais seulement pour la durée du corps de la fonction". Ceci n'est, en aucun cas, en conflit avec les règles de propriété de la norme unique_ptr parce que l'objet est simplement emprunté pour une courte période, il n'y a pas de transfert de propriété réel (si vous prêtez votre voiture à quelqu'un, lui remettez-vous le titre de propriété ?)

Ainsi, même s'il peut sembler mauvais (du point de vue de la conception, des pratiques de codage, etc.) d'extraire la référence (ou même le pointeur brut) de l'espace de travail de l'utilisateur. unique_ptr mais il ne l'est pas, car il est parfaitement conforme aux règles de propriété fixées par la Commission européenne. unique_ptr . Et puis, bien sûr, il y a d'autres avantages, comme une syntaxe propre, l'absence de restriction aux seuls objets appartenant à l'utilisateur. unique_ptr et ainsi de suite.

0voto

Personnellement, j'évite de tirer une référence d'un pointeur ou d'un pointeur intelligent. Car que se passe-t-il si le pointeur est nullptr ? Si vous changez la signature en ceci :

bool func(BaseClass& base, int other_arg);

Vous pourriez avoir à protéger votre code contre les déréférences de pointeurs nuls :

if (the_unique_ptr)
  func(*the_unique_ptr, 10);

Si la classe est le seul propriétaire du pointeur, la deuxième alternative de Martinho semble plus raisonnable :

func(the_unique_ptr.get(), 10);

Alternativement, vous pouvez utiliser std::shared_ptr . Cependant, s'il y a une seule entité responsable pour delete El std::shared_ptr Les frais généraux ne sont pas rentables.

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