Динамически создаваемые ограждения
Я прочитал статью об ограничителях видимости ( 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));
// ...
}
Кстати, спасибо за ответ выше. Это было информативно и познавательно для меня по-разному.