Std :: unique_ptr est-il nécessaire pour connaître la définition complète de T?

J’ai un code dans un en-tête qui ressemble à ceci:

#include  class Thing; class MyClass { std::unique_ptr my_thing; }; 

Si j’inclus cet en-tête dans un fichier cpp qui n’inclut pas la définition du type Thing , cela ne comstack pas sous VS2010-SP1:

1> C: \ Program Files (x86) \ Microsoft Visual Studio 10.0 \ VC \ include \ mémoire (2067): erreur C2027: utilisation du type non défini ‘Thing’

Remplacez std::unique_ptr par std::shared_ptr et il comstack.

Donc, je suppose que c’est l’implémentation actuelle du VS2010 std::unique_ptr qui nécessite la définition complète et qui est totalement dépendante de l’implémentation.

Ou est-ce? Y at-il quelque chose dans ses exigences standard qui rend impossible l’implémentation de std::unique_ptr avec une déclaration à terme uniquement? Cela semble étrange, car il ne devrait contenir qu’un point sur Thing , n’est-ce pas?

Adopté à partir d’ ici .

La plupart des modèles de la bibliothèque standard C ++ exigent qu’ils soient instanciés avec des types complets. Cependant, shared_ptr et unique_ptr sont des exceptions partielles . Certains, mais pas tous, peuvent être instanciés avec des types incomplets. La motivation à cela est de prendre en charge des idiomes tels que pimpl en utilisant des pointeurs intelligents, sans risquer un comportement indéfini.

Un comportement non défini peut se produire lorsque vous avez un type incomplet et que vous appelez delete sur celui-ci:

 class A; A* a = ...; delete a; 

Ce qui précède est le code légal. Il va comstackr. Votre compilateur peut ou non émettre un avertissement pour le code ci-dessus comme ci-dessus. Quand il s’exécute, de mauvaises choses vont probablement arriver. Si vous êtes très chanceux, votre programme tombera en panne. Cependant, un résultat plus probable est que votre programme fuira silencieusement de la mémoire car ~A() ne sera pas appelé.

Utiliser auto_ptr dans 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à que l’aide shared_ptr et unique_ptr . L’utilisation de l’un de ces pointeurs intelligents vous permettra de vous en sortir avec un type incomplet, sauf s’il est nécessaire d’avoir un type complet. Et plus important encore, quand 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 comstack, alors vous avez utilisé un type complet partout où vous en avez besoin.

 class A { class impl; std::unique_ptr ptr_; // ok! public: A(); ~A(); // ... }; 

shared_ptr et unique_ptr nécessitent un type complet à différents endroits. Les raisons sont obscures, concernant un deleter dynamic par rapport à 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 requirejs. Juste coder, et si vous vous trompez, le compilateur vous le dira.

Cependant, si cela vous est utile, voici un tableau qui documente plusieurs membres de shared_ptr et unique_ptr qui concerne les exigences de complétude. Si le membre nécessite un type complet, alors l’entrée a un “C”, sinon l’entrée de la table est remplie par “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 | +------------------------+---------------+---------------+ 

Toute opération nécessitant des conversions de pointeur nécessite des types complets à la fois pour unique_ptr et shared_ptr .

Le constructeur unique_ptr{A*} ne peut avoir un A incomplet que si le compilateur n’est pas obligé de configurer un appel à ~unique_ptr() . Par exemple, si vous mettez l’ unique_ptr sur le tas, vous pouvez vous en passer avec un A incomplet. Plus de détails sur ce point peuvent être trouvés dans la réponse de BarryTheHatchet ici .

Le compilateur a besoin de la définition de Thing pour générer le destructeur par défaut pour MyClass. Si vous déclarez explicitement le destructeur et déplacez son implémentation (vide) dans le fichier CPP, le code doit être compilé.

Cela ne dépend pas de l’implémentation. La raison pour laquelle cela fonctionne est parce que shared_ptr détermine le destructeur approprié à appeler à l’exécution – il ne fait pas partie de la signature de type. Cependant, le destructeur de unique_ptr fait partie de son type, et il doit être connu à la compilation.

Il semble que les réponses actuelles ne permettent pas de comprendre pourquoi le constructeur (ou le destructeur) par défaut est un problème, mais que les réponses vides déclarées dans cpp ne le sont pas.

Voici ce qui se passe:

Si la classe externe (c’est-à-dire MyClass) n’a pas de constructeur ou de destructeur, le compilateur génère les valeurs par défaut. Le problème est que le compilateur insère essentiellement le constructeur / destructeur vide par défaut dans le fichier .hpp. Cela signifie que le code du constructeur / destructeur par défaut est compilé avec le binary de l’exécutable hôte, et non avec les fichiers binarys de votre bibliothèque. Cependant, ces définitions ne peuvent pas vraiment construire les classes partielles. Donc, lorsque l’éditeur de liens entre dans le fichier binary de votre bibliothèque et tente d’obtenir le constructeur / destructeur, il n’en trouve aucun et vous obtenez une erreur. Si le code constructeur / destructeur était dans votre fichier .cpp, votre fichier binary de bibliothèque est disponible pour la liaison.

Donc, cela n’a rien à voir avec l’utilisation de unique_ptr au lieu de shared_ptr pour le scénario ci-dessus tant que vous utilisez des compilateurs modernes (l’ancien compilateur VC ++ peut avoir un bogue dans l’implémentation unique_ptr mais VC ++ 2015 fonctionne correctement sur ma machine).

La morale de l’histoire est que votre en-tête doit restr libre de toute définition de constructeur / destructeur. Il ne peut contenir que leur déclaration. Par exemple, ~MyClass()=default; dans hpp ne fonctionnera pas. Si vous autorisez le compilateur à insérer un constructeur ou un destructeur par défaut, vous obtiendrez une erreur de l’éditeur de liens.

Une autre remarque: si vous obtenez toujours cette erreur même après avoir un constructeur et un destructeur dans le fichier cpp, il est très probable que votre bibliothèque ne soit pas compilée correctement. Par exemple, une fois j’ai simplement changé le type de projet de console en bibliothèque en VC ++ et j’ai eu cette erreur parce que VC ++ n’a pas ajouté le symbole de préprocesseur _LIB et que cela produit exactement le même message d’erreur.

La définition complète de la chose est requirejse au moment de l’instanciation du modèle. C’est la raison exacte pour laquelle l’idiome pimpl est compilé.

Si ce n’était pas possible, les gens ne poseraient pas de questions comme celle-ci .

Juste pour être complet:

Header: Ah

 class B; // forward declaration class A { std::unique_ptr ptr_; // ok! public: A(); ~A(); // ... }; 

Source A.cpp:

 class B { ... }; // class definition A::A() { ... } A::~A() { ... } 

La définition de la classe B doit être vue par le constructeur, le destructeur et tout ce qui pourrait implicitement supprimer B. (Bien que le constructeur n’apparaisse pas dans la liste ci-dessus, dans VS2017, même le constructeur a besoin de la définition de B.) qu’en cas d’exception dans le constructeur, l’unique_ptr est détruit à nouveau.)

Comme pour moi,

 QList> controllers; 

Il suffit d’inclure l’en-tête …

 #include