web-dev-qa-db-fra.com

Passer unique_ptr aux fonctions

J'essaie de "moderniser" du code existant.

  • J'ai une classe qui a actuellement une variable membre "Device * device_".
  • Il utilise new pour créer une instance dans un code d'initialisation et a un "delete device_" dans le destructory.
  • Les fonctions membres de cette classe appellent plusieurs d'autres fonctions qui prennent un périphérique * comme paramètre.

Cela fonctionne bien, mais pour "moderniser" mon code, j'ai pensé que je devais changer la variable à définir comme "std::unique_ptr<Device> device_" et supprimez l'appel explicite à supprimer, ce qui rend le code plus sûr et généralement meilleur.

Ma question est la suivante -

  • Comment dois-je ensuite passer la variable device _ à toutes les fonctions qui en ont besoin en tant que paramètre?

Je peux appeler .get pour obtenir le pointeur brut dans chaque appel de fonction. Mais cela semble moche et gaspille certaines des raisons d'utiliser un unique_ptr en premier lieu.

Ou je peux changer la fonction every pour qu'au lieu de prendre un paramètre de type "Device *", il prenne maintenant un paramètre de type "std :: unique_ptr &". Ce qui (pour moi) obscurcit quelque peu les prototypes de fonctions et les rend difficiles à lire.

Quelle est la meilleure pratique pour cela? Ai-je manqué d'autres options?

46
jcoder

Dans le style Modern C++, il existe deux concepts clés:

  • La possession
  • Nullité

Propriété concerne le propriétaire d'un objet/d'une ressource (dans ce cas, une instance de Device). Les différents std::unique_ptr, boost::scoped_ptr ou std::shared_ptr concerne la propriété.

Nullité est cependant beaucoup plus simple: il exprime simplement si un objet donné peut être nul, et ne se soucie de rien d'autre, et certainement pas de la propriété!


Vous étiez à droite pour déplacer l'implémentation de votre classe vers unique_ptr (en général), bien que vous souhaitiez un pointeur intelligent avec une sémantique de copie approfondie si votre objectif est d'implémenter un PIMPL.

Cela indique clairement que votre classe est le seul responsable de ce morceau de mémoire et traite soigneusement toutes les différentes façons dont la mémoire aurait pu fuir autrement.


D'un autre côté, la plupart des tilisateurs des ressources se moquaient de leur propriété.

Tant qu'une fonction ne conserve pas de référence à un objet (stockez-la dans une carte ou quelque chose), tout ce qui compte, c'est que la durée de vie de l'objet dépasse la durée de l'appel de fonction.

Ainsi, choisir comment passer le paramètre dépend de son possible Nullité:

  • Jamais nul? Passez un référence
  • Peut-être nul? Passez un pointeur, un simple pointeur nu ou une classe de type pointeur (avec un piège sur null par exemple)

44
Matthieu M.

Ça dépend vraiment. Si une fonction doit s'approprier l'unique_ptr, alors sa signature doit prendre un unique_ptr<Device> Bv valeur et l'appelant doit std::move Le pointeur. Si la propriété n'est pas un problème, je garderais la signature du pointeur brut et passerais le pointeur unique_ptr en utilisant get(). Ce n'est pas moche si la fonction en question ne reprend pas la propriété.

14
juanchopanza

J'utiliserais std::unique_ptr const&. L'utilisation d'une référence non constante donnera à la fonction appelée la possibilité de réinitialiser votre pointeur.
Je pense que c'est une belle façon d'exprimer que votre fonction appelée peut utiliser le pointeur mais rien d'autre.
Donc, pour moi, cela facilitera la lecture de l'interface. Je sais que je n'ai pas besoin de tripoter le pointeur qui m'est passé.

7
mkaes

La meilleure pratique est probablement de ne pas utiliser std::unique_ptr dans ce cas, bien que cela dépende. (Vous ne devez généralement pas avoir plus d'un pointeur brut vers un objet alloué dynamiquement dans une classe. Bien que cela dépende également.) La seule chose que vous ne voulez pas faire dans ce cas est de contourner std::unique_ptr (et comme vous l'avez remarqué, std::unique_ptr<> const& est un peu lourd et obscurcissant). Si c'est le seul pointeur alloué dynamiquement dans l'objet, je resterais avec le pointeur brut et le delete dans le destructeur. S'il existe plusieurs pointeurs de ce type, j'envisagerais de reléguer chacun d'eux dans une classe de base distincte (où ils peuvent toujours être des pointeurs bruts).

2
James Kanze

Cela peut ne pas être faisable pour vous, mais remplacer chaque occurrence de Device* Par const unique_ptr<Device>& Est un bon début.

Vous ne pouvez évidemment pas copier unique_ptr Et vous ne voulez pas le déplacer. Le remplacement par une référence à unique_ptr Permet au corps des corps des fonctions existantes de continuer à fonctionner.

Il y a maintenant un piège, vous devez passer par const & Pour empêcher les callees de faire unique_ptr.reset() ou unique_ptr().release(). Notez que cela transmet toujours un pointeur modifiable à l'appareil. Avec cette solution, vous n'avez aucun moyen facile de passer un pointeur ou une référence à un const Device.

1
J.N.