Pergunta

Eu li o artigo sobre guardas de escopo (Genérico: mude a maneira como você escreve código seguro para exceção-para sempre) em DDJ e eu entendemos seu uso comum.

No entanto, o uso comum é instanciar uma proteção específica na pilha para uma operação específica, por exemplo:

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

Mas e se eu quiser agendar operações de limpeza em tempo de execução, por exemplo, quando eu tiver um loop:

{
   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
   }
}

Obviamente, o exemplo acima não funcionaria, já que topSecret seria fechado junto com o por alcance. Eu gostaria de um padrão de guarda de escopo, onde posso facilmente fazer fila de operações de limpeza que eu determino ser necessário em tempo de execução. Existe algo assim disponível?

Não posso empurrar objetos de proteção de escopo para uma fila padrão, porque o objeto original (o que estou empurrando) seria descartado no processo. Que tal empurrar os guardas de pilha alocados por heap e usar uma fila que exclui seus membros no DTOR? Alguém tem uma abordagem mais inteligente?

Foi útil?

Solução

Parece que você não aprecia Raii pelo que é. Esses guardas de escopo são bons ocasionalmente para as coisas locais ("escopo"), mas você deve tentar evitá -las em favor do que Raii realmente deveria fazer: encapsular um recurso em um objeto. O arquivo de tipo* realmente não é bom nisso.

Aqui está uma alternativa:

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 em "C ++ 0x" (próximo padrão C ++):

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

EDIT: Como eu gosto muito de tipos móveis em C ++ 0x e você demonstrou interesse por ele: eis como você pode usar exclusivo_ptr em combinação com o arquivo* sem qualquer sobrecarga de contagem de ref:

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() );
}

(não testado)

Não deixe de conferir Blog de Dave sobre tipos de valor móvel eficiente

Outras dicas

Huh, acontece que o guarda do escopo do DDJ é "móvel", não no sentido C ++ 0x, mas no mesmo sentido que um Auto_PTR é móvel: durante a cópia, o novo guarda "rejeita" a velha guarda (como auto_ptr's cópia CTOR chama o antigo Auto_ptr :: Release).

Então eu posso simplesmente manter um queue<ScopeGuard> E vai funcionar:

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));
   // ...
}

A propósito, obrigado pela resposta acima. Foi informativo e educacional para mim de maneiras diferentes.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top