web-dev-qa-db-fra.com

shared_ptr magic :)

M. Lidström et moi avons eu un argument :)

L'affirmation de M. Lidström est qu'une construction shared_ptr<Base> p(new Derived); ne nécessite pas que Base ait un destructeur virtuel:

Armen Tsirunyan : "Vraiment? Est-ce que le shared_ptr se nettoiera correctement? Pourriez-vous s'il vous plaît, dans ce cas, démontrer comment cet effet pourrait être mis en œuvre? "

Daniel Lidström : "Le shared_ptr utilise son propre destructeur pour supprimer l'instance Concrete. C'est ce qu'on appelle RAII dans le Communauté C++. Je vous conseille d'apprendre tout ce que vous pouvez sur RAII. Cela rendra votre codage C++ beaucoup plus facile lorsque vous utiliserez RAII dans toutes les situations. "

Armen Tsirunyan : "Je connais RAII, et je sais aussi que finalement le destructeur shared_ptr peut supprimer le px stocké lorsque pn atteint 0. Mais si px avait un pointeur de type statique vers Base et un pointeur de type dynamique vers Derived, alors à moins que Base n'ait un destructeur virtuel, cela entraînera un comportement indéfini . Corrigez-moi si je me trompe."

Daniel Lidström : "Le shared_ptr sait que le type statique est Concrete. Il le sait depuis que je l'ai passé dans son constructeur ! Cela ressemble un peu à la magie, mais je peux vous assurer que c'est par conception et extrêmement agréable. "

Alors, jugez-nous. Comment est-il possible (si c'est le cas) d'implémenter shared_ptr sans exiger des classes polymorphes d'avoir un destructeur virtuel? Merci d'avance

85
Armen Tsirunyan

Oui, il est possible d'implémenter shared_ptr de cette façon. Boost le fait et la norme C++ 11 requiert également ce comportement. Comme flexibilité supplémentaire, shared_ptr gère plus qu'un simple compteur de référence. Un dénommé deleter est généralement placé dans le même bloc de mémoire qui contient également les compteurs de référence. Mais la partie amusante est que le type de ce suppresseur ne fait pas partie du type shared_ptr. Ceci est appelé "effacement de type" et est fondamentalement la même technique utilisée pour implémenter les "fonctions polymorphes" boost :: function ou std :: function pour masquer le type de foncteur réel. Pour que votre exemple fonctionne, nous avons besoin d'un constructeur basé sur des modèles:

template<class T>
class shared_ptr
{
public:
   ...
   template<class Y>
   explicit shared_ptr(Y* p);
   ...
};

Donc, si vous l'utilisez avec vos classes Base et Derived ...

class Base {};
class Derived : public Base {};

int main() {
   shared_ptr<Base> sp (new Derived);
}

... le constructeur basé sur un modèle avec Y = Derived est utilisé pour construire l'objet shared_ptr. Le constructeur a ainsi la possibilité de créer l'objet deleter et les compteurs de référence appropriés et stocke un pointeur sur ce bloc de contrôle en tant que membre de données. Si le compteur de référence atteint zéro, le suppresseur créé précédemment et compatible avec les dérivés sera utilisé pour éliminer l'objet.

Le standard C++ 11 a à dire à propos de ce constructeur (20.7.2.2.1):

Nécessite: p doit être convertible en T*. Y doit être un type complet. L'expression delete p Doit être bien formée, avoir un comportement bien défini et ne pas lever d'exceptions.

Effets: Construit un objet shared_ptrque possède le pointeur p.

Et pour le destructeur (20.7.2.2.2):

Effets: Si *this Est vide ou partage la propriété avec une autre instance de shared_ptr (use_count() > 1), il n'y a pas d'effets secondaires. Sinon, si *this Possède un objet p et un suppresseur d, d(p) est appelé. Sinon, si *this Possède un pointeur p et que delete p Est appelé.

(l'accent est mis sur la police en gras).

70
sellibitze

Lorsque shared_ptr est créé, il stocke un objet deleter à l'intérieur de lui-même. Cet objet est appelé lorsque le shared_ptr est sur le point de libérer la ressource pointée. Puisque vous savez comment détruire la ressource au moment de la construction, vous pouvez utiliser shared_ptr avec des types incomplets. Celui qui a créé le shared_ptr y a stocké un deleter correct.

Par exemple, vous pouvez créer un suppresseur personnalisé:

void DeleteDerived(Derived* d) { delete d; } // EDIT: no conversion needed.

shared_ptr<Base> p(new Derived, DeleteDerived);

p appellera DeleteDerived pour détruire l'objet pointé. L'implémentation le fait automatiquement.

28
ybungalobill

Simplement,

shared_ptr utilise une fonction de suppression spéciale qui est créée par un constructeur qui utilise toujours le destructeur de l'objet donné et non le destructeur de Base, c'est un peu de travail avec la méta-programmation de modèle, mais ça marche.

Quelque chose comme ca

template<typename SomeType>
shared_ptr(SomeType *p)
{
   this->destroyer = destroyer_function<SomeType>(p);
   ...
}
16
Artyom