4

我在 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
   }
}

显然,上面的例子是行不通的,因为它会与for范围topSecret一起被关闭。我想要一个范围保护模式,我可以在其中轻松地将我确定在运行时需要的清理操作排队。有这样的东西吗?

我无法将范围保护对象推送到标准队列中,因为原始对象(我正在推送的对象)将在此过程中被解雇。推送堆分配的堆栈保护并使用删除其成员在 dtor 上的队列怎么样?有没有人有更聪明的方法?

4

2 回答 2

6

看来您并不欣赏 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() );
}

(未经测试)

请务必查看Dave 关于高效可移动价值类型的博客

于 2009-10-14T11:32:50.230 回答
0

嗯,原来 DDJ 范围保护是“可移动的”,不是在 C++0x 的意义上,而是在与 auto_ptr 可移动的相同意义上:在复制 ctor 期间,新的保护“解除”旧的保护(如 auto_ptr 的复制 ctor 调用旧的auto_ptr::release)。

所以我可以简单地保留 aqueue<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));
   // ...
}

顺便说一下,谢谢楼上的回答。它以不同的方式为我提供了丰富的信息和教育。

于 2009-10-14T13:35:35.253 回答