Est-ce que std:: ptr unique est 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 <memory>

class Thing;

class MyClass
{
    std::unique_ptr< Thing > my_thing;
};

si j'inclut cet en-tête dans un cpp qui n'inclut pas la définition de type Thing , alors cela ne compile pas sous VS2010-SP1:

1>C:Program fichiers (x86)Microsoft Visual Studio 10.0VCincludememory(2067): erreur C2027: utilisation de type non défini "Chose"

remplacer std::unique_ptr par std::shared_ptr et il compile.

donc, je devine que c'est la mise en œuvre actuelle de VS2010 std::unique_ptr qui nécessite la définition complète et c'est totalement dépendant de la mise en œuvre.

ou est-ce? Y a-t-il quelque chose dans ses exigences standard qui rend impossible pour la mise en œuvre de std::unique_ptr de fonctionner avec une déclaration forward seulement? C'est bizarre , ça ne devrait tenir qu'un pointeur sur Thing , Non?

207
demandé sur JasonMArcher 2011-05-16 04:11:51

7 réponses

adopté de ici .

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

comportement non défini peut se produire lorsque vous avez un type incomplet et vous appelez delete sur elle:

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

ci-dessus est le code légal. Compiler. Votre compilateur peut ou non émettre un avertissement pour le code ci-dessus comme le code ci-dessus. Quand il s'exécute, de mauvaises choses vont probablement se produire. Si vous avez de la chance, votre programme s'écrasera. Cependant un résultat plus probable est que votre programme fuira silencieusement la mémoire comme ~A() ne sera pas appelé.

utiliser auto_ptr<A> dans l'exemple ci-dessus n'aide pas. Vous obtenez toujours le même comportement indéfini comme si vous aviez utilisé un pointeur brut.

néanmoins, utiliser des classes incomplètes dans certains endroits est très utile! C'est là que shared_ptr et unique_ptr aident. L'utilisation de l'un de ces pointeurs intelligents permettra de vous sortir avec un type incomplète, sauf lorsqu'il est nécessaire d'avoir un type complètes. Et le plus important, quand il est nécessaire d'avoir un type complètes, vous obtiendrez une erreur de compilation si vous essayez d'utiliser le pointeur intelligent avec un type incomplète à ce point.

Non plus un comportement indéfini:

si votre code compile, alors vous avez utilisé un type complet partout où vous devez.

class A
{
    class impl;
    std::unique_ptr<impl> ptr_;  // ok!

public:
    A();
    ~A();
    // ...
};

shared_ptr et unique_ptr requièrent un type complet à des endroits différents. Les raisons sont obscures, ayant à voir avec un deleter dynamique vs deleter statique. Les raisons précises ne sont pas importantes. En fait, dans la plupart des codes, il n'est pas vraiment important pour vous de savoir exactement où un type complet est requis. Il suffit de 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 en ce qui concerne les exigences d'exhaustivité. Si le membre a besoin d'un type complet, alors l'entrée a un "C", sinon l'entrée de la table est remplie avec "je".

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 pointeurs nécessite des types complets pour unique_ptr et shared_ptr .

le constructeur unique_ptr<A>{A*} ne peut s'en tirer avec un A incomplet que si le compilateur n'est pas obligé de configurer un appel à ~unique_ptr<A>() . Par exemple, si vous mettez le unique_ptr sur le tas, vous pouvez vous en sortir avec une incomplète A . Plus de détails sur ce point peut être trouvée dans BarryTheHatchet réponse ici .

279
répondu Howard Hinnant 2017-05-23 11:47:17

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 devrait être compilé.

37
répondu Igor Nazarenko 2011-05-16 00:17:11

Ce n'est pas dépendant de l'implémentation. La raison pour laquelle cela fonctionne est que shared_ptr détermine le bon destructeur à 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 doit être connu au moment de la compilation.

9
répondu Puppy 2017-08-01 07:07:06

il semble que les réponses actuelles ne précisent pas exactement pourquoi le constructeur par défaut (ou destructeur) est un problème alors que les réponses vides déclarées dans cpp ne le sont pas.

Voici ce qui se passe:

si la classe externe (i.e. MyClass) n'a pas de constructeur ou de destructeur, alors le compilateur génère les valeurs par défaut. Le problème avec ceci est que le compilateur insère essentiellement le constructeur/destructeur vide par défaut dans le .php fichier. Cela signifie que le code de contructor/destructor 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. Donc quand linker va dans le binaire de votre bibliothèque et essaie d'obtenir le constructeur/destructeur, il ne trouve pas et vous obtenez l'erreur. Si le code constructeur / destructeur était dans votre .cpp alors votre binaire de bibliothèque a cela Disponible pour le lien.

donc, cela n'a rien à voir avec l'utilisation de unique_ptr au lieu de shared_ptr pour le scénario ci-dessus aussi longtemps que vous utilisez des compilateurs modernes (l'ancien compilateur VC++ peut avoir un bug dans l'implémentation unique_ptr mais VC++ 2015 fonctionne très bien sur ma machine).

ainsi morale de l'histoire est que votre en-tête doit rester libre de toute définition 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 le constructeur par défaut ou destructeur, vous obtiendrez un linker erreur.

note d'un autre côté: si vous obtenez toujours cette erreur même après avoir eu le constructeur et le destructeur dans le fichier cpp, alors la raison la plus probable est que votre bibliothèque n'est pas compilée correctement. Par exemple, une fois j'ai simplement changé le type de projet de la Console à la bibliothèque en VC++ et j'ai eu cette erreur parce que VC++ n'a pas ajouté le symbole de préprocesseur _LIB et cela a produit exactement le même message d'erreur.

3
répondu ShitalShah 2017-02-10 11:56:26

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 cela n'était pas possible, les gens ne poseraient pas de questions comme ce .

1
répondu BЈовић 2017-05-23 11:47:17

Juste pour être complet:

en-tête: A. h

class B; // forward declaration

class A
{
    std::unique_ptr<B> 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. et cela a du sens quand on considère que dans le cas d'une exception dans le constructeur unique_ptr est de nouveau détruit.)

0
répondu Joachim 2018-01-12 15:31:48

quant à moi,

QList<QSharedPointer<ControllerBase>> controllers;

incluez juste l'en-tête ...

#include <QSharedPointer>
-1
répondu Sanbrother 2018-08-22 06:37:14