J'ai du code dans un en-tête qui ressemble à ceci:
#include <memory>
class Thing;
class MyClass
{
std::unique_ptr< Thing > my_thing;
};
Si Thing
j'inclus cet en-tête dans un cpp qui n'inclut pas la définition de type, cela ne se compile pas sous VS2010-SP1:
1> C: \ Program Files (x86) \ Microsoft Visual Studio 10.0 \ VC \ include \ memory (2067): erreur C2027: utilisation du type non défini 'Thing'
Remplacez std::unique_ptr
par std::shared_ptr
et il compile.
Donc, je suppose que c'est l'implémentation actuelle du VS2010 std::unique_ptr
qui nécessite la définition complète et est totalement dépendante de l'implémentation.
Ou est-ce? Y a-t-il quelque chose dans ses exigences standard qui rend impossible std::unique_ptr
la mise en œuvre de 's avec une déclaration directe uniquement? Cela semble étrange car il ne devrait contenir qu'un pointeur Thing
, n'est-ce pas?
shared_ptr
/unique_ptr
" de Howard Hinnant. Le tableau à la fin devrait répondre à votre question.Réponses:
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
etunique_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
: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
etunique_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.
shared_ptr
etunique_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
etunique_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".Toutes les opérations nécessitant des conversions de pointeurs nécessitent des types complets pour
unique_ptr
etshared_ptr
.Le
unique_ptr<A>{A*}
constructeur ne peut s'en tirer avec un incompletA
que si le compilateur n'est pas obligé d'établir un appel à~unique_ptr<A>()
. Par exemple, si vous mettez leunique_ptr
sur le tas, vous pouvez vous en sortir avec un incompletA
. Plus de détails sur ce point peuvent être trouvés dans la réponse de BarryTheHatchet ici .la source
unique_ptr
comme une variable membre d'une classe, juste explicitement déclarer un destructor (et constructeur) dans la déclaration de classe (dans le fichier d' en- tête) et procéder à définir les dans le fichier source (et placez l'en-tête avec la déclaration complète de la classe pointée dans le fichier source) pour empêcher le compilateur d'inscrire automatiquement le constructeur ou le destructeur dans le fichier d'en-tête (ce qui déclenche l'erreur). stackoverflow.com/a/13414884/368896 aide également à me le rappeler.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é.
la source
MyClass::~MyClass() = default;
dans le fichier d'implémentation semble moins susceptible d'être supprimé par inadvertance plus tard sur la route par quelqu'un qui suppose que le corps du destructeur a été effacé plutôt que laissé délibérément en blanc.default
ed etdelete
d.MyClass::~MyClass() = default
ne le déplace pas dans le fichier d'implémentation sur Clang. (encore?)Cela ne dépend pas de l'implémentation. La raison pour laquelle cela fonctionne est parce qu'il
shared_ptr
détermine le destructeur correct à appeler au moment de l'exécution - il ne fait pas partie de la signature de type. Cependant,unique_ptr
le destructeur de 's fait partie de son type, et il doit être connu au moment de la compilation.la source
Il semble que les réponses actuelles ne déterminent pas exactement pourquoi le constructeur (ou le destructeur) par défaut pose problème, mais pas les réponses vides déclarées dans cpp.
Voici ce qui se passe:
Si la classe externe (c'est-à-dire MyClass) n'a pas de constructeur ou de destructeur, alors le compilateur génère ceux 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 binaire de l'exécutable hôte, pas avec les binaires de votre bibliothèque. Cependant, ces définitions ne peuvent pas vraiment construire les classes partielles. Ainsi, lorsque l'éditeur de liens va dans le binaire de votre bibliothèque et essaie d'obtenir le constructeur / destructeur, il n'en trouve aucun et vous obtenez une erreur. Si le code constructeur / destructeur était dans votre .cpp, alors votre binaire de bibliothèque a celui-ci disponible pour la liaison.
Cela n'a rien à voir avec l'utilisation de unique_ptr ou shared_ptr et d'autres réponses semblent être un bogue confondant possible dans l'ancien VC ++ pour la mise en œuvre unique_ptr (VC ++ 2015 fonctionne très bien sur ma machine).
La morale de l'histoire est que votre en-tête doit rester libre de toute définition de constructeur / destructeur. Il ne peut contenir que leur déclaration. Par exemple,
~MyClass()=default;
en hpp ne fonctionnera pas. Si vous autorisez le compilateur à insérer le constructeur ou le destructeur par défaut, vous obtiendrez une erreur de l'éditeur de liens.Une autre note latérale: Si vous obtenez toujours cette erreur même après avoir le constructeur et le destructeur dans le fichier cpp, la raison est probablement que votre bibliothèque n'est pas correctement compilée. Par exemple, une fois, j'ai simplement changé le type de projet de la console en bibliothèque dans VC ++ et j'ai eu cette erreur car VC ++ n'a pas ajouté le symbole du préprocesseur _LIB et cela a produit exactement le même message d'erreur.
la source
Juste pour être complet:
En-tête: Ah
Source A.cpp:
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, unique_ptr est à nouveau détruit.)
la source
La définition complète de la chose est requise au moment de l'instanciation du modèle. C'est la raison exacte pour laquelle l'idiome pimpl se compile.
Si ce n'était pas possible, les gens ne poseraient pas de questions comme celle-ci .
la source
La réponse simple est d'utiliser à la place shared_ptr.
la source
Comme pour moi,
Il suffit d'inclure l'en-tête ...
la source