Динамически создаваемые ограждения

Я прочитал статью об ограничителях видимости ( Generic: Измените способ написания кода, исключающего безопасность - навсегда) в DDJ, и я понимаю их общее использование.

Тем не менее, общее использование состоит в том, чтобы создать конкретную защиту стека в стеке для конкретной операции, например:

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

но что, если я хочу запланировать операции очистки во время выполнения, например, когда у меня есть цикл:

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

Очевидно, что приведенный выше пример не будет работать, так какtopSecret будет закрыт вместе с областью применения. Я хотел бы использовать шаблон защиты области видимости, в котором я мог бы так же легко ставить в очередь операции очистки, которые я считаю необходимыми во время выполнения. Есть ли что-то подобное?

Я не могу поместить объекты охраны области видимости в стандартную очередь, потому что исходный объект (который я помещаю) будет отклонен в процессе. Как насчет того, чтобы поместить выделенные в кучу средства защиты стека и использовать очередь, которая удаляет своих членов в dtor? У кого-нибудь есть более умный подход?

2 ответа

Решение

Кажется, ты не ценишь RAII за то, что он есть. Эти ограничители области видимости хороши в некоторых случаях для локальных ("охват") вещей, но вы должны стараться избегать их в пользу того, что RAII действительно должен делать: инкапсулировать ресурс в объекте. Тип FILE* действительно не очень хорош в этом.

Вот альтернатива:

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

Или же:

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

Или в "C++0x" (следующий стандарт C++):

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

Редактировать: Так как мне очень нравятся подвижные типы в C++ 0x, и вы проявили к нему интерес: вот как вы можете использовать unique_ptr в сочетании с FILE* без каких-либо накладных расходов на повторный подсчет:

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

(Непроверенные)

Обязательно ознакомьтесь с блогом Дэйва об эффективных типах перемещаемых значений.

Да, оказывается, охранник области видимости DDJ является "подвижным", не в смысле C++0x, а в том же смысле, что auto_ptr является подвижным: во время копирования ctor новый защитник "отклоняет" старую охрану (как auto_ptr copy ctor вызывает старый auto_ptr:: release).

Так что я могу просто держать queue<ScopeGuard> и это будет работать:

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

Кстати, спасибо за ответ выше. Это было информативно и познавательно для меня по-разному.

Другие вопросы по тегам