37 votes

Pourquoi les suppresseurs de pts partagés doivent-ils être CopyConstructibles ?

En C++11 std::shared_ptr possède quatre constructeurs auxquels on peut passer des objets délétères d de type D . Les signatures de ces constructeurs sont les suivantes :

template<class Y, class D> shared_ptr(Y * p, D d);
template<class Y, class D, class A> shared_ptr(Y * p, D d, A a);
template <class D> shared_ptr(nullptr_t p, D d);
template <class D, class A> shared_ptr(nullptr_t p, D d, A a);

La norme exige en [util.smartptr.shared.const] tipo D pour être CopyConstructible. Pourquoi cela est-il nécessaire ? Si shared_ptr fait des copies de d alors lequel de ces suppresseurs pourrait être appelé ? Ne serait-il pas possible qu'un shared_ptr seulement pour garder un seul suppresseur dans les parages ? Qu'est-ce que cela signifie pour un shared_ptr a propre un suppresseur si d peut être copié ?

Quelle est la raison d'être de l'exigence de CopyConstructible ?

PS : Cette exigence pourrait compliquer l'écriture de suppresseurs pour shared_ptr . unique_ptr semble avoir de bien meilleures exigences pour son suppresseur.

21voto

AndyG Points 3298

Cette question m'a laissé suffisamment perplexe pour que j'envoie un courriel à Peter Dimov (responsable de l'implémentation de l'outil de gestion de la qualité de l'eau). boost::shared_ptr et impliqué dans la normalisation de std::shared_ptr )

Voici l'essentiel de ses propos (reproduits avec son autorisation) :

Mon avis est que le suppresseur devait être CopyConstructible vraiment seulement comme une relique de C++03 où la sémantique de déplacement n'existait pas.

Vous avez raison. Quand shared_ptr a été spécifié les références rvalue n'existaient pas encore. Aujourd'hui, nous devrions pouvoir nous en sortir en exigeant que nothrow move-constructible.

Il y a une subtilité dans le fait que lorsque

pi_ = new sp_counted_impl_pd<P, D>(p, d);

jets, d doit être laissé intact pour le nettoyage d(p) pour travailler, mais je mais je pense que ce ne serait pas un problème (bien que je n'aie pas réellement essayé de rendre l'implémentation facile à déplacer).
[...]
Je pense qu'il n'y aura pas de problème pour le l'implémentation de le définir de sorte que lorsque le new jets, d sera laissé dans son état d'origine.

Si nous allons plus loin et permettons D d'avoir un constructeur de mouvement de lancer, les choses deviennent plus compliquées. Mais nous ne le ferons pas. :-)

2voto

Ap31 Points 2448

La différence entre les suppresseurs dans std::shared_ptr y std::unique_ptr c'est que shared_ptr est effacé par type, alors qu'en unique_ptr Le type de suppresseur fait partie du modèle.

Voici l'explication de Stephan T. Lavavej comment l'effacement de type mène à l'exigence de CopyConstructible en std::function .

Quant à la raison de cette différence entre les types de pointeurs, elle a été abordée à plusieurs reprises sur SO, par ex. aquí .

Une citation de ce que S.T.L. a dit :

Un "gotcha" très surprenant, je dirais, est que la std::function nécessite des objets de fonction CopyConstructible, ce qui est plutôt inhabituel dans la STL.

Habituellement, la STL est paresseuse dans le sens où elle n'a pas besoin de choses au départ : si j'ai quelque chose comme un std::list de type T , T n'a pas besoin d'être moins-que-comparable ; seulement si vous appelez la fonction membre list<T>::sort alors il faut que ce soit moins que comparable.

La règle de base du langage qui sous-tend ce système est que les définitions des fonctions membres d'un modèle de classe ne sont pas instanciées tant qu'elles ne sont pas réellement nécessaires et que les corps n'existent pas dans un certain sens tant que vous ne les appelez pas.

C'est généralement cool - cela signifie que vous ne payez que pour ce dont vous avez besoin, mais std::function est spécial en raison de l'effacement des types, car lorsque vous construisez le fichier std::function à partir d'un objet appelable F il doit générer tout ce dont vous pourriez avoir besoin à partir de cet objet. F parce qu'il va effacer son type. Il a besoin de toutes les opérations dont il pourrait avoir besoin, qu'elles soient utilisées ou non.

Ainsi, si vous construisez un std::function à partir d'un objet appelable F , F est absolument nécessaire au moment de la compilation pour être CopyConstructible. Ceci est vrai même si F sera déplacé dans le std::function donc même si vous lui donnez une valeur r et même si vous ne copiez jamais std::function n'importe où dans votre programme, F est toujours tenu d'être CopyConstructible.

Vous obtiendrez une erreur du compilateur qui vous le dira - peut-être horrible, peut-être agréable - selon ce que vous obtiendrez.

Il ne peut tout simplement pas stocker des objets mobiles à fonction unique. Il s'agit d'une limitation de conception causée en partie par le fait que std::function remonte à boost/TR1, avant les références à la valeur r, et dans un certain sens, elle ne pourra jamais être corrigée avec std::function L'interface de l'entreprise est telle qu'elle est actuellement.

Des alternatives sont à l'étude, peut-être que nous pouvons avoir une "fonction mobile" différente, donc nous obtiendrons probablement une sorte de wrapper effacé par type qui peut stocker la fonction mobile seulement dans le futur, mais std::function dans l'état actuel de c++17, cela n'est pas possible, soyez-en conscient.

-2voto

mefyl Points 256

Parce que shared_ptr sont destinés à être copiés, et n'importe laquelle de ces copies pourrait avoir à supprimer l'objet, donc ils doivent tous avoir accès à un suppresseur. Garder un seul deleter nécessiterait de recompter le deleter lui-même. Si vous voulez vraiment que cela se produise, vous pouvez utiliser un objet imbriqué std::shared_ptr comme suppresseur, mais ça semble un peu exagéré.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X