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) :
- L'adresse d'un bloc de mémoire où se trouve l'objet souhaité. ( le bien )
- 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 )
- 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 )
- 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é).