2009-10-14 6 views
4

J'ai lu l'article sur les gardes de portée (Generic: Change the Way You Write Exception-Safe Code — Forever) dans DDJ et je comprends leur utilisation commune.gardes de portée créés Dynamiquement

Cependant, l'utilisation commune est d'instancier un garde de pile particulier sur la pile pour une opération particulière, par exemple .:

{ 
    FILE* topSecret = fopen("cia.txt"); 
    ON_BLOCK_EXIT(std::fclose, topSecret); 
    ... use topSecret ... 
} // topSecret automagically closed 

mais si je veux planifier les opérations de nettoyage dans l'exécution, par exemple quand j'ai une boucle:

{ 
    vector<FILE*> topSecretFiles; 
    for (int i=0; i<numberOfFiles; ++i) 
    { 
     char filename[256]; 
     sprintf(filename, "cia%d.txt", i); 
     FILE* topSecret = fopen(filename); 
     topSecretFiles.push_back(topSecret); 
     ON_BLOCK_EXIT(std::fclose, topSecret); // no good 
    } 
} 

De toute évidence, l'exemple ci-dessus ne fonctionnerait pas, étant donné que topSecret serait fermé en même temps que la pour portée. Je voudrais un motif de protection de champ où je peux juste la file d'attente aussi facilement les opérations de nettoyage que je RESOLUE à être nécessaire lors de l'exécution. Y at-il quelque chose comme ça disponible?

Je ne peux pas pousser des objets de garde scope dans une file d'attente standard, faire l'objet original (celui que je fais pression) serait rejeté dans le processus. Que diriez-vous de pousser des gardes de pile affectés par tas et d'utiliser une file d'attente qui supprime ses membres sur dtor? Est-ce que quelqu'un a une approche plus intelligente?

Répondre

6

Il vous semble ne pas apprécier RAII pour ce qu'elle est. Ces gardes de portée sont parfois sympas pour des choses locales ("scope") mais vous devriez essayer de les éviter en faveur de ce que RAII est supposé faire: encapsuler une ressource dans un objet. Le type FILE * n'est vraiment pas bon pour ça.

est ici une alternative:

void foo() { 
    typedef std::tr1::shared_ptr<FILE> file_sptr; 
    vector<file_sptr> bar; 
    for (...) { 
     file_sptr fsp (std::fopen(...), std::fclose); 
     bar.push_back(fsp); 
    } 
} 

Ou:

void foo() { 
    typedef std::tr1::shared_ptr<std::fstream> stream_sptr; 
    vector<stream_sptr> bar; 
    for (...) { 
     file_sptr fsp (new std::fstream(...)); 
     bar.push_back(fsp); 
    } 
} 

Ou dans "C++ 0x" (future norme C++):

void foo() { 
    vector<std::fstream> bar; 
    for (...) { 
     // streams will become "movable" 
     bar.push_back(std::fstream(...)); 
    } 
} 

Edit: Depuis que je l'aime types mobiles en C++ 0x tellement et vous avez montré un intérêt pour cela: Voici comment vous pouvez utiliser unique_ptr en combinaison avec FILE * sans tête ref comptage:

struct file_closer { 
    void operator()(FILE* f) const { if (f) std::fclose(f); } 
}; 

typedef std::unique_ptr<FILE,file_closer> file_handle; 

file_handle source() { 
    file_handle fh (std::fopen(...)); 
    return fh; 
} 

int sink(file_handle fh) { 
    return std::fgetc(fh.get()); 
} 

int main() { 
    return sink(source()); 
} 

(non testé)

Assurez-vous de vérifier Dave's blog on efficient movable value types

+0

Oui, vous avez raison RAII est préférable pour une destructor d'une ressource (par exemple une poignée de fichier). Je n'utilise que des gardes de portée pour des choses qui seraient difficiles à représenter en tant que «ressource», par ex. rapport (OperationStart); ON_BLOCK_EXIT (rapport, OperationEnd); faireQuelque chose(); – Ilya

+0

Malheureusement, je n'ai pas encore TR1 dans mon compilateur, donc je ne peux pas utiliser shared_ptr. Cependant, un vecteur/une file d'attente de auto_ptr pourrait bien fonctionner (en supposant que j'allouerais en tas mes gardes de portée et les pousserais dans le vecteur/la file d'attente). Je suis intrigué d'apprendre que certaines classes C++ deviendront "mobiles" (n'était pas au courant de cette terminologie). Est-ce fait avec des recomptages? Peut-être que je devrais rendre les gardes de portée "mobiles" aussi? – Ilya

+4

Un vecteur/queue de auto_ptr <...> n'est clairement pas une bonne idée, car auto_ptrs ne sont pas copiables! –

0

Huh, se révèle la garde de champ DDJ est "mobile", et non pas dans le C++ 0x sens, mais dans le même sens qu'un auto_ptr est mobile: lors de la copie cteur, la nouvelle garde « rejette » la vieille garde (comme le ctor copie de auto_ptr appelle auto_ptr de l'ancien :: libérer).

donc je peux simplement garder un queue<ScopeGuard> et il va travailler:

queue<ScopeGuard> scopeGuards; 

// ... 

for (...) 
{ 
    // the temporary scopeguard is being neutralized when copied into the queue, 
    // so it won't cause a double call of cleanupFunc 
    scopeGuards.push_back(MakeScopeGuard(cleanupFunc, arg1)); 
    // ... 
} 

Par ailleurs, je vous remercie de la réponse ci-dessus. C'était instructif et éducatif pour moi de différentes manières.

+0

Oui. Son constructeur de copie "se déplace" ce qui le rend aussi dangereux que auto_ptr. Pour une version C++ 0xified, consultez mon article: http://pizer.wordpress.com/2008/11/22/scope-guards-revisited-c0x-style/ – sellibitze

+0

Ne le faites pas! C'est faux. C'est mauvais. Ça ne marchera pas. L'objet de garde qu'ils montrent dans cet article DDJ est aussi mauvais que auto_ptr. Il se déplace sur une copie. Ce n'est pas comment un type de valeur devrait se comporter! – sellibitze

+0

Aussi, ScopeGuard est juste un typedef pour "ScopeGuardBase const &" si je me souviens bien. Utilisez simplement shared_ptr pour gérer les pointeurs FILE! ;-) – sellibitze

Questions connexes