2010-10-10 16 views
73

Mr. Lidström and I had an argument :)magie shared_ptr :)

la demande de M. Lidström est qu'une construction shared_ptr<Base> p(new Derived); ne nécessite pas de base d'avoir une destructor virtuelle:

Armen Tsirunyan: « Vraiment Est-ce que la shared_ptr nettoyer 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 destructor pour supprimer l'instance béton Ceci est connu comme RAII dans le C++ communauté Mon conseil est que vous apprenez tout ce que vous pouvez sur RAII Il fera de votre... Codage C++ tellement plus facile lorsque vous utilisez RAII dans toutes les situations. "

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

Daniel Lidström: « Le shared_ptr connaît le type statique est en béton Il sait depuis je l'ai passé dans son constructeur semble un peu comme par magie, mais je peux vous assurer qu'il est par la conception et extrêmement agréable.! "

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

+3

Vous pourriez avoir lié au [fil d'origine] (http://stackoverflow.com/questions/3899688/default-virtual-dtor/3899726). –

+0

@Darin: Je l'ai fait. – sbi

+7

Une autre chose intéressante est que 'shared_ptr p (new Derived)' détruira aussi l'objet 'Derived' par son destructeur, que ce soit' virtual' ou non. – dalle

Répondre

66

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. En tant que flexibilité supplémentaire, shared_ptr gère plus qu'un simple compteur de référence.Un soi-disant suppresseur 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 cacher le type du foncteur. Pour que votre exemple, nous avons besoin d'un constructeur basé sur un modèle:

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

Donc, si vous utilisez avec vos classes de base et dérivés ...

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

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

... le constructeur avec Y = basé sur un modèle Derived est utilisé pour construire l'objet shared_ptr. Le constructeur a ainsi la possibilité de créer les compteurs d'objets et de références de compteur appropriés et de stocker 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 précédemment créé et déri- vé sera utilisé pour disposer de l'objet.

La norme C++ 11 a ceci à dire au sujet 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 émettre d'exceptions.

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

...

Et pour la destructor (20.7.2.2.2):

Effets: Si *this est vide ou d'actions avec une autre propriété shared_ptr instance (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ée. Sinon, si *this possède un pointeur p, delete p est appelée.

(l'emphase utilisant la police en gras est la mienne).

+0

+1 pour l'extrait de code pour afficher le type d'effacement en cours. – legends2k

+0

'la prochaine norme exige également ce comportement': (a) Quelle norme et (b) pouvez-vous s'il vous plaît fournir une référence (à la norme)? – kevinarpe

13

Simplement,

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

Quelque chose comme ça

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

hmm ... intéressant, je commence à le croire :) –

+1

@Armen Tsirunyan Vous devriez avoir jeté un coup d'oeil dans la description de conception de la shared_ptr avant de commencer le discusson. Cette 'capture du deleter' est l'une des caractéristiques essentielles de shared_ptr ... –

+5

@ paul_71: Je suis d'accord avec vous. D'un autre côté, je crois que cette discussion a été utile non seulement pour moi, mais aussi pour d'autres personnes qui ne connaissaient pas ce fait sur le shared_ptr. Donc, je suppose que ce n'était pas un grand péché de commencer ce fil de toute façon :) –

26

Lorsque shared_ptr est créé, il stocke un Deleter objet à l'intérieur lui-même. Cet objet est appelé lorsque 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 a stocké un suppresseur correct là.

Par exemple, vous pouvez créer un destructeur 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 pointu. L'implémentation le fait automatiquement.

+4

+1 pour la remarque sur les types incomplets, très pratique lorsque vous utilisez un 'shared_ptr' comme attribut. –