2010-07-09 4 views
1

Je suppose que je ne comprends pas complètement comment les destructeurs fonctionnent en C++. Voici l'exemple de programme je l'ai écrit pour recréer la question:C++: Pourquoi le destructeur est-il appelé ici?

#include <iostream> 
#include <memory> 
#include <vector> 

using namespace std; 

struct Odp 
{ 
    int id; 

    Odp(int id) 
    { 
     this->id = id; 
    } 

    ~Odp() 
    { 
     cout << "Destructing Odp " << id << endl; 
    } 
}; 

typedef vector<shared_ptr<Odp>> OdpVec; 

bool findOdpWithID(int id, shared_ptr<Odp> shpoutOdp, OdpVec& vec) 
{ 
    shpoutOdp.reset(); 

    for (OdpVec::iterator iter = vec.begin(); iter < vec.end(); iter++) 
    { 
     Odp& odp = *(iter->get()); 
     if (odp.id == id) 
     { 
      shpoutOdp.reset(iter->get()); 
      return true; 
     } 
    } 

    return false; 
} 

int main() 
{ 
    OdpVec vec; 

    vec.push_back(shared_ptr<Odp>(new Odp(0))); 
    vec.push_back(shared_ptr<Odp>(new Odp(1))); 
    vec.push_back(shared_ptr<Odp>(new Odp(2))); 

    shared_ptr<Odp> shOdp; 
    bool found = findOdpWithID(0, shOdp, vec); 
    found = findOdpWithID(1, shOdp, vec); 
} 

Juste avant main() conclut, la sortie de ce programme est:

Destructing Odp 0 
Destructing Odp 1 

Pourquoi cela? Je conserve une référence à chacune des instances Odp dans le vecteur. Cela a-t-il quelque chose à voir avec le passage d'un shared_ptr par référence?

MISE À JOUR Je pensais que shared_ptr::reset décrémenté le comte ref, basé sur MSDN:

Les opérateurs tous Decremente le compteur de référence pour la ressource actuellement détenue par * ce

mais peut-être que je le comprends mal?

MISE À JOUR 2: On dirait que cette version de findOdpWithID() ne provoque pas la destructor à appeler:

bool findOdpWithID(int id, shared_ptr<Odp> shpoutOdp, OdpVec& vec) 
{ 
    for (OdpVec::iterator iter = vec.begin(); iter < vec.end(); iter++) 
    { 
     Odp& odp = *(iter->get()); 
     if (odp.id == id) 
     { 
      shpoutOdp = *iter; 
      return true; 
     } 
    } 

    return false; 
} 
+0

Vous mentionnez dans un commentaire ci-dessous que vous essayez de décrémenter le compteur. Vous ne devriez pas vous inquiéter à ce sujet. C'est le travail du pointeur partagé de s'inquiéter des détails d'implémentation comme le nombre de ref. Vous devriez juste l'utiliser. Vous pouvez penser à shared_ptr comme le pointeur java, il fait tout le travail dont vous avez besoin dans les coulisses. –

+1

Ce n'est pas une bonne question. Vous faites allusion à un "problème" mais ne nous dites pas ce que c'est, et vous vous plaignez de la sortie de votre programme mais ne nous dites pas ce que vous attendiez à la place. Nous ne pouvons pas deviner le comportement que vous attendez dans votre esprit, d'autant plus que le comportement réel - étant, par nature, le bon comportement C++ - est ce que nous attendons! –

Répondre

12

Cette ligne ici est probablement ce qui vous trébuche.

shpoutOdp.reset(iter->get()); 

Ce que vous faites ici est d'obtenir (par get()) le pointeur nu du pointeur intelligent, qui ne dispose d'aucune information de suivi de référence sur elle, en disant ensuite shpoutOdp de se remettre à point nus aiguille. Lorsque shpoutOdp est détruit, il ne sait pas qu'il y a un autre shared_ptr qui pointe vers la même chose, et shpoutOdp procède à la destruction de la chose vers laquelle il pointe.

Vous devez juste faire

shpoutOdp = *iter; 

qui maintiendra le nombre de référence correctement. En passant, reset() décrémente le compteur de référence (et ne le détruit que si le compte atteint 0).

+0

Mais si je fais 'shpoutOdp = * iter', je n'ai pas besoin d'utiliser' reset() ', car le nombre de ref sera automatiquement décrémenté pour moi, non? –

+0

Oui, vous l'avez. – richardwb

2

shared_ptr::reset détruit le contenu déjà dans le shared_ptr. Si vous souhaitez affecter uniquement cette référence shared_ptr unique, attribuez-lui simplement.

EDIT: En réponse à des commentaires, vous pouvez le corriger en modifiant le corps de votre boucle à:

if ((*iter)->id == id) 
{ 
    shpoutOdp = *iter; 
    return true; 
} 

EDIT2: Que tous dit, pourquoi ne vous utilisez std :: find_if ici?

#include <iostream> 
#include <memory> 
#include <vector> 
#include <algorithm> //for std::find_if 
#include <functional> //for std::bind 

struct Odp 
{ 
    int id; 

    int GetId() 
    { 
     return id; 
    } 

    Odp(int id) 
    { 
     this->id = id; 
    } 

    ~Odp() 
    { 
     std::cout << "Destructing Odp " << id << std::endl; 
    } 
}; 

typedef std::vector<shared_ptr<Odp> > OdpVec; 

int main() 
{ 
    OdpVec vec; 

    vec.push_back(std::shared_ptr<Odp>(new Odp(0))); 
    vec.push_back(std::shared_ptr<Odp>(new Odp(1))); 
    vec.push_back(std::shared_ptr<Odp>(new Odp(2))); 

    OdpVec::iterator foundOdp = std::find_if(vec.begin(), vec.end(), 
     std::bind(std::equal_to<int>(), 0, std::bind(&Odp::GetId,_1))); 
    bool found = foundOdp != vec.end(); 
} 
+0

Je voulais juste décrémenter le nombre de ref. Comment puis je faire ça? –

+1

@Rosarch: Autorisez simplement la destruction de l'ancien shared_ptr. Vous pouvez lui assigner un null shared_ptr si vous le souhaitez. –

+0

Pour être honnête, je trouve 'find_if' moins évident. N'importe qui peut regarder une boucle 'for' et comprendre ce qui se passe, mais quelqu'un qui ne connaît pas STL se demandera quel est le bruit de ce' std :: bind'. –

1

La bonne chose à propos de shared_ptr est qu'il gère le ref-comptage en interne. Vous n'avez pas besoin de l'incrémenter ou de le décrémenter manuellement ever.(Et c'est pourquoi shared_ptr ne pas vous permettre de le faire non plus)

Lorsque vous appelez reset, il définit simplement le shared_ptr courant pour pointer vers un autre objet (ou nul). Cela signifie qu'il y a maintenant une référence de moins à l'objet pointé avant le reset, donc en ce sens, le compteur ref a été décrémenté. Mais ce n'est pas une fonction que vous devriez appeler pour décrémenter le compteur ref.

Vous n'avez jamais besoin de faire cela. Laissez simplement le shared_ptr sortir du cadre, et il prend soin de décrémenter le nombre de références.

C'est un exemple de RAII en action. La ressource que vous devez gérer (dans ce cas, l'objet pointé par shared_ptr) est liée à un objet affecté par la pile (shared_ptr lui-même), de sorte que sa durée de vie est automatiquement gérée. Le destructeur shared_ptr s'assure que l'objet pointé est libéré le cas échéant.

4

Tant de choses qui sont utilisées presque correctement:

bool findOdpWithID(int id, shared_ptr<Odp> shpoutOdp, OdpVec& vec) 

Ici le paramètre shpoutOdp est une copie du paramètre d'entrée. Pas un gros problème étant donné que c'est un pointeur partagé, mais ce n'est probablement pas ce que vous vouliez. Vous avez probablement voulu passer par référence sinon pourquoi le passer à la fonction en premier lieu.

shpoutOdp.reset(); 

La réinitialisation d'un paramètre tel qu'il est passé dans.
Est-ce que cela signifie qu'il pourrait être sale (alors pourquoi avoir comme un paramètre d'entrée), il fait la fonction renvoie un pointeur partagé à la suite si vous voulez passer quelque chose. N'utilisez pas les pointeurs partagés sauf si vous en avez vraiment besoin (et vous en avez vraiment besoin si vous en avez vraiment besoin). Il n'est pas nécessaire d'extraire le pointeur pour savoir à quoi pointe le pointeur et vous risquez davantage de faire des erreurs parce que vous gérez des pointeurs. La ligne de sécurité équivalent (r) est:

Odp& odp = *(*iter); // The first * gets a reference to the shared pointer. 
        // The second star gets a reference to what the shared 
        //pointer is pointing at 

C'est là où tout va mal:

shpoutOdp.reset(iter->get()); 

Vous créez un nouveau pointeur Partagée partir d'un pointeur. Malheureusement, le pointeur est déjà géré par un autre pointeur partagé. Donc maintenant vous avez deux pointeurs partagés qui pensent qu'ils possèdent le pointeur et vont le supprimer quand ils sortent de la portée (le premier sort de la portée à la fin de la fonction car c'est une copie du paramètre d'entrée (plutôt qu'une référence)). La bonne chose à faire est de faire une mission. Ensuite, les pointeurs partagés savent qu'ils partagent un pointeur:

shpoutOdp = *iter; // * converts the iterator into a shared pointer reference 

La ligne suivante mais pas tout à fait tort ne suppose que les itérateurs utilisés sont un accès aléatoire (ce qui est vrai pour le vecteur).

for (OdpVec::iterator iter = vec.begin(); iter < vec.end(); iter++) 

Mais ce qui rend le code plus fragile comme un simple changement dans le typedef OdpVec cassera le code sans aucun avertissement. Donc, pour rendre cela plus cohérent avec l'utilisation normale de l'itérateur, utilisez!= lors de la vérification par rapport à end() et aussi préférer l'opérateur de pré-incrémentation:

for (OdpVec::iterator iter = vec.begin(); iter != vec.end(); ++iter) 
+0

Merci pour les commentaires détaillés. Je vous en suis reconnaissant. –

+0

Aussi, quelle est la raison de préférer l'opérateur pré-incrément? –

+1

@Rosarch: Voir par ex. [Y a-t-il une différence de performance entre i ++ et ++ i en C++?] (Http://stackoverflow.com/questions/24901/is-there-a-performance-difference-between-i-and-i-in-c). –

Questions connexes