Adopté d' ici .
La plupart des modèles de la bibliothèque standard C ++ nécessitent qu'ils soient instanciés avec des types complets. Cependant shared_ptr
et unique_ptr
sont des exceptions partielles . Certains, mais pas tous leurs membres peuvent être instanciés avec des types incomplets. La motivation pour cela est de supporter des idiomes tels que pimpl en utilisant des pointeurs intelligents, et sans risquer un comportement indéfini.
Un comportement indéfini peut se produire lorsque vous avez un type incomplet et que vous l'appelez delete
:
class A;
A* a = ...;
delete a;
Ce qui précède est un code légal. Il compilera. Votre compilateur peut ou non émettre un avertissement pour le code ci-dessus comme ci-dessus. Lors de son exécution, de mauvaises choses se produiront probablement. Si vous êtes très chanceux, votre programme se bloquera. Cependant, un résultat plus probable est que votre programme perdra silencieusement de la mémoire car ~A()
il ne sera pas appelé.
L'utilisation auto_ptr<A>
de l'exemple ci-dessus n'aide pas. Vous obtenez toujours le même comportement indéfini que si vous aviez utilisé un pointeur brut.
Néanmoins, l'utilisation de classes incomplètes à certains endroits est très utile! C'est là shared_ptr
et unique_ptr
aider. L'utilisation de l'un de ces pointeurs intelligents vous permettra de vous en sortir avec un type incomplet, sauf lorsqu'il est nécessaire d'avoir un type complet. Et surtout, lorsqu'il est nécessaire d'avoir un type complet, vous obtenez une erreur de compilation si vous essayez d'utiliser le pointeur intelligent avec un type incomplet à ce stade.
Plus de comportement indéfini:
Si votre code compile, vous avez utilisé un type complet partout où vous en avez besoin.
class A
{
class impl;
std::unique_ptr<impl> ptr_; // ok!
public:
A();
~A();
// ...
};
shared_ptr
et unique_ptr
nécessitent un type complet à différents endroits. Les raisons sont obscures, ayant à voir avec un deleter dynamique vs un deleter statique. Les raisons précises ne sont pas importantes. En fait, dans la plupart des codes, il n'est pas vraiment important que vous sachiez exactement où un type complet est requis. Juste du code, et si vous vous trompez, le compilateur vous le dira.
Cependant, au cas où cela vous serait utile, voici un tableau qui documente plusieurs membres shared_ptr
et unique_ptr
concernant les exigences d'exhaustivité. Si le membre requiert un type complet, l'entrée a un "C", sinon l'entrée de table est remplie de "I".
Complete type requirements for unique_ptr and shared_ptr
unique_ptr shared_ptr
+------------------------+---------------+---------------+
| P() | I | I |
| default constructor | | |
+------------------------+---------------+---------------+
| P(const P&) | N/A | I |
| copy constructor | | |
+------------------------+---------------+---------------+
| P(P&&) | I | I |
| move constructor | | |
+------------------------+---------------+---------------+
| ~P() | C | I |
| destructor | | |
+------------------------+---------------+---------------+
| P(A*) | I | C |
+------------------------+---------------+---------------+
| operator=(const P&) | N/A | I |
| copy assignment | | |
+------------------------+---------------+---------------+
| operator=(P&&) | C | I |
| move assignment | | |
+------------------------+---------------+---------------+
| reset() | C | I |
+------------------------+---------------+---------------+
| reset(A*) | C | C |
+------------------------+---------------+---------------+
Toutes les opérations nécessitant des conversions de pointeurs nécessitent des types complets pour unique_ptr
et shared_ptr
.
Le unique_ptr<A>{A*}
constructeur ne peut s'en tirer avec un incomplet A
que si le compilateur n'est pas obligé d'établir un appel à ~unique_ptr<A>()
. Par exemple, si vous mettez le unique_ptr
sur le tas, vous pouvez vous en sortir avec un incomplet A
. Plus de détails sur ce point peuvent être trouvés dans la réponse de BarryTheHatchet ici .
shared_ptr
/unique_ptr
" de Howard Hinnant. Le tableau à la fin devrait répondre à votre question.