2010-05-19 4 views
1

Je rafraîchis mes connaissances en C++ après ne pas l'avoir utilisé en colère pendant un certain nombre d'années. En écrivant du code pour implémenter une structure de données pour la pratique, je voulais m'assurer que mon code était sûr. J'ai donc essayé d'utiliser std::auto_ptr dans ce que je pense être un moyen approprié. Un peu Simplifier, voici ce que j'ai:Utilisation idiomatique de auto_ptr pour transférer la propriété à un conteneur

class Tree 
{ 
public: 
    ~Tree() { /* delete all Node*s in the tree */ } 
    void insert(const string& to_insert); 
    ... 

private: 
    struct Node { 
     ... 
     vector<Node*> m_children; 
    }; 

    Node* m_root; 
}; 

template<T> 
void push_back(vector<T*>& v, auto_ptr<T> x) 
{ 
    v.push_back(x.get()); 
    x.release(); 
} 

void Tree::insert(const string& to_insert) 
{ 
    Node* n = ...; // find where to insert the new node 
    ... 
    push_back(n->m_children, auto_ptr<Node>(new Node(to_insert)); 
    ... 
} 

Je suis enroulant la fonction qui placerait le pointeur dans le récipient, vector::push_back, et se fondant sur l' pour veiller à ce que la Node* par valeur est l'argument auto_ptr supprimé si le redimensionnement vector échoue.

Est-ce une utilisation idiomatique de auto_ptr pour enregistrer un peu de passe-partout dans mon Tree::insert? Des améliorations que vous pouvez suggérer? Sinon, je devrais avoir quelque chose comme:

Node* n = ...; // find where to insert the new node 
auto_ptr<Node> new_node(new Node(to_insert)); 
n->m_children.push_back(new_node.get()); 
new_node.release(); 

quel type de encombre ce qui aurait été une seule ligne de code si je n'étais pas se soucier de la sécurité d'exception et une fuite de mémoire. (En fait, je me demandais si je pouvais poster mon code entier (environ 300 lignes) et demander aux gens de le critiquer pour une utilisation C++ idiomatique en général, mais je ne suis pas sûr que ce genre de question soit approprié sur stackoverflow .)

Répondre

2

il n'est pas idiomatiques d'écrire votre propre conteneur: il est assez exceptionnel, et pour la plupart utiles que pour apprendre à écrire des conteneurs. En tout cas, il n'est certainement pas idiomatique d'utiliser std::autp_ptr avec des conteneurs standards. En fait, c'est faux, car les copies de std::auto_ptr ne sont pas équivalentes: une seule auto_ptr possède une pointe à un moment donné.

En ce qui concerne l'utilisation de idiomatiques std::auto_ptr, vous devez toujours nommer votre auto_ptr sur la construction:

int wtv() { /* ... */ } 
void trp(std::auto_ptr<int> p, int i) { /* ... */ } 

void safe() { 
    std::auto_ptr<int> p(new int(12)); 
    trp(p, wtv()); 
} 

void danger() { 
    trp(std::auto_ptr<int>(new int(12)), wtv()); 
} 

Parce que la norme C++ permet d'évaluer les arguments dans un ordre arbitraire, l'appel à danger() est dangereux. Dans l'appel à trp() dans danger(), le compilateur peut allouer l'entier, puis créer le auto_ptr et enfin appeler wtv(). Ou, le compilateur peut allouer un nouvel entier, appelez wtv(), et enfin créer le auto_ptr. Si wtv() déclenche une exception, alors danger() peut ou non présenter une fuite.

Dans le cas de safe(), cependant, étant donné que le auto_ptr est construit à priori, RAII garantit qu'il nettoiera correctement si wtv() émet ou non une exception.

+0

En réécrivant vos propres conteneurs, bien sûr, je chercherais normalement une implémentation existante. (Dans ce cas, la vraie structure de données que j'écrivais était une Patricia.) Mais comme mentionné, écrire ceci était plus pour la pratique que quelque chose de pratique. Je suis conscient de l'impossibilité d'utiliser auto_ptrs dans les conteneurs STL. Le point sur l'évaluation de l'argument est bon! Dans mon cas, je devrais être OK puisque l'autre argument est juste une recherche de membre, mais je suppose que ce serait bien d'être cohérent et de toujours nommer mon auto_ptrs pour éviter ce problème potentiel. – heycam

0

J'aime l'idée de déclarer la propriété de pointeur. C'est l'une des grandes caractéristiques de C++ 0x, std::unique_ptr s. Cependant std::auto_ptr est si difficile à comprendre et mortel est même légèrement trompé Je suggère de l'éviter entièrement.

2

Oui c'est.

Par exemple, voir l'interface de la bibliothèque Boost.Pointer Container.Les différents conteneurs de pointeurs disposent tous d'une fonction d'insertion prenant un auto_ptr qui garantit sémantiquement qu'ils prennent possession (ils ont aussi la version du pointeur brut mais hey: p).

Il existe cependant d'autres moyens de réaliser ce que vous faites en matière de sécurité d'exception, car il est uniquement interne ici. Pour le comprendre, vous devez comprendre ce qui pourrait mal se passer (c.-à-d. Lancer), puis réordonner vos instructions afin que les opérations qui peuvent être effectuées soient terminées avant que les effets secondaires ne se produisent.

Par exemple, en prenant de votre message:

auto_ptr<Node> new_node(new Node(to_insert)); // 1 
n->m_children.push_back(new_node.get());  // 2 
new_node.release();       // 3 

Vérifions chaque ligne,

  1. Le constructeur peut lancer (par exemple si le CopyConstructor du type lance), dans ce cas, cependant, vous êtes garanti que new effectuera le nettoyage pour vous
  2. L'appel à push_back peut lancer un std::bad_alloc si la mémoire est épuisée. C'est la seule erreur possible que la copie d'un pointeur est une opération
  3. sans jet Ceci est garanti de ne pas jeter

Si vous regardez attentivement, vous faire remarquer que vous ne seriez pas à vous inquiéter si vous pouviez en quelque sorte 2 ont été exécutés avant 1. il est en effet possible:

n->m_children.reserve(n->m_children.size() + 1); 
n->m_children.push_back(new Node(to_insert)); 

L'appel à reserve peut jeter (bad_alloc), mais si elle se termine normalement vous êtes alors assuré qu'aucune réallocation se produira jusqu'à ce que size devient égale à capacity et vous essayez un autre insertion.

L'appel à new peut tomber si le jet du constructeur, auquel cas new effectuera le nettoyage, si elle complète vous vous retrouvez avec un pointeur qui est immédiatement inséré dans le vector ... qui est garanti de ne pas jeter à cause de la ligne ci-dessus.

Ainsi, l'utilisation de auto_ptr peut être remplacée ici. C'était néanmoins une bonne idée, mais comme cela a été noté, vous devriez vous abstenir d'exécuter l'initialisation RAII dans une évaluation de fonction.

+0

Merci pour l'indication sur l'utilisation de 'reserve' pour éviter le' auto_ptr'. Je l'avais en fait dans mon code avant de passer à 'auto_ptr', parce que je pensais que ce serait plus clair. – heycam

Questions connexes