J'utilise le pimpl-idiom avec std::unique_ptr
:
class window {
window(const rectangle& rect);
private:
class window_impl; // defined elsewhere
std::unique_ptr<window_impl> impl_; // won't compile
};
Cependant, j'obtiens une erreur de compilation concernant l'utilisation d'un type incomplet, à la ligne 304 dans <memory>
:
Application non valide de '
sizeof
' à un type incomplet 'uixx::window::window_impl
'
Pour autant que je sache, std::unique_ptr
devrait pouvoir être utilisé avec un type incomplet. Est-ce un bogue dans libc ++ ou est-ce que je fais quelque chose de mal ici?
Réponses:
Voici quelques exemples de
std::unique_ptr
types incomplets. Le problème réside dans la destruction.Si vous utilisez pimpl avec
unique_ptr
, vous devez déclarer un destructeur:car sinon le compilateur en génère un par défaut, et il a besoin d'une déclaration complète de
foo::impl
pour cela.Si vous avez des constructeurs de modèles, vous êtes vissé, même si vous ne construisez pas le
impl_
membre:Au niveau de l'espace de noms, l'utilisation
unique_ptr
ne fonctionnera pas non plus:puisque le compilateur doit savoir ici comment détruire cet objet de durée statique. Une solution de contournement est:
la source
foo::~foo() = default;
dans le fichier srcComme l'a mentionné Alexandre C. , le problème se résume à
window
la définition implicite du destructeur dans des endroits où le type dewindow_impl
est encore incomplet. En plus de ses solutions, une autre solution de contournement que j'ai utilisée consiste à déclarer un foncteur Deleter dans l'en-tête:Notez que l'utilisation d'une fonction Deleter personnalisée empêche l'utilisation de
std::make_unique
(disponible à partir de C ++ 14), comme déjà discuté ici .la source
utiliser un suppresseur personnalisé
Le problème est que
unique_ptr<T>
doit appeler le destructeurT::~T()
dans son propre destructeur, son opérateur d'affectation de déplacement et launique_ptr::reset()
fonction membre (uniquement). Cependant, ceux-ci doivent être appelés (implicitement ou explicitement) dans plusieurs situations PIMPL (déjà dans le destructeur de la classe externe et l'opérateur d'affectation de déplacement).Comme nous l' avons souligné dans une autre réponse, une façon d'éviter que est de déplacer toutes les opérations qui nécessitent
unique_ptr::~unique_ptr()
,unique_ptr::operator=(unique_ptr&&)
etunique_ptr::reset()
dans le fichier source où la classe d'aide de Pimpl est réellement défini.Cependant, cela est plutôt gênant et défie le point même de l'idiome de bouton à un certain degré. Une solution beaucoup plus propre qui évite tout ce qui est d'utiliser un deleter personnalisé et à ne déplacer sa définition que dans le fichier source où réside la classe d'assistance de bouton. Voici un exemple simple:
Au lieu d'une classe deleter distincte, vous pouvez également utiliser une fonction libre ou un
static
membre defoo
conjointement avec un lambda:la source
Vous avez probablement des corps de fonction dans le fichier .h de la classe qui utilisent un type incomplet.
Assurez-vous que dans votre fenêtre .h pour la classe, vous n'avez que la déclaration de fonction. Tous les corps de fonction pour la fenêtre doivent être dans un fichier .cpp. Et pour window_impl aussi ...
Btw, vous devez ajouter explicitement la déclaration du destructeur pour la classe windows dans votre fichier .h.
Mais vous NE POUVEZ PAS mettre de corps de dtor vide dans votre fichier d'en-tête:
Doit être juste une déclaration:
la source
Pour ajouter aux réponses des autres sur le suppresseur personnalisé, dans notre "bibliothèque d'utilitaires" interne, j'ai ajouté un en-tête d'aide pour implémenter ce modèle commun (
std::unique_ptr
d'un type incomplet, connu seulement de certains TU pour par exemple éviter de longs temps de compilation ou pour fournir juste une poignée opaque pour les clients).Il fournit l'échafaudage commun pour ce modèle: une classe deleter personnalisée qui invoque une fonction deleter définie en externe, un alias de type pour a
unique_ptr
avec cette classe deleter et une macro pour déclarer la fonction deleter dans un TU qui a une définition complète de la type. Je pense que cela a une utilité générale, alors voici:la source
Ce n'est peut-être pas la meilleure solution, mais parfois vous pouvez utiliser shared_ptr à la place. Bien sûr, c'est un peu exagéré, mais ... comme pour unique_ptr, j'attendrai peut-être 10 ans de plus jusqu'à ce que les fabricants de normes C ++ décident d'utiliser lambda comme suppresseur.
Un autre côté. Selon votre code, il peut arriver que lors de la destruction, window_impl soit incomplet. Cela pourrait être une raison de comportement indéfini. Voir ceci: Pourquoi, vraiment, la suppression d'un type incomplet est un comportement indéfini?
Donc, si possible, je définirais un objet très basique pour tous vos objets, avec destructeur virtuel. Et tu es presque bon. Vous devez simplement garder à l'esprit que le système appellera un destructeur virtuel pour votre pointeur, vous devez donc le définir pour chaque ancêtre. Vous devez également définir la classe de base dans la section héritage comme virtuelle (voir ceci pour plus de détails).
la source