【问题标题】:Dynamically created scope guards动态创建的范围保护
【发布时间】:2009-10-14 10:40:25
【问题描述】:

我已阅读有关 DDJ 中范围保护 (Generic: Change the Way You Write Exception-Safe Code — Forever) 的文章,我了解它们的常见用途。

但是,常见的用途是在堆栈上为特定操作实例化特定的堆栈保护,例如:

{
    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 将与 for 范围一起关闭。我想要一个范围保护模式,我可以轻松地将我确定在运行时需要的清理操作排队。有这样的东西吗?

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

【问题讨论】:

    标签: c++ design-patterns scopeguard


    【解决方案1】:

    您似乎并不欣赏 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's blog on efficient movable value types

    【讨论】:

    • 是的,你说得对,RAII 更适合资源的析构函数(例如文件句柄)。我只对那些难以表示为“资源”的东西使用范围保护,例如报告(操作开始); ON_BLOCK_EXIT(报告,操作结束); doSomething();
    • 很遗憾我的编译器中还没有 TR1,所以我不能使用 shared_ptr。但是, auto_ptr 的向量/队列可能只是工作(假设我将堆分配我的范围保护并将它们推入向量/队列)。我很想知道一些 C++ 类将变得“可移动”(不知道这个术语)。这是通过引用计数完成的吗?也许我也应该让范围保护装置“可移动”?
    • auto_ptr<...> 的向量/队列显然不是一个好主意,因为 auto_ptrs 不可复制!
    • 您可以改用 Boost 的 shared_ptr 实现。 “可移动”类型不是通过间接和引用计数来实现的。它需要一个新的 C++0x 特性:右值引用。有关“移动语义”的深入文章系列,请查看cpp-next.com/archive/category/value-semantics。我的建议是 (a) 重新设计,(b) 使用 Boost 的 shared_ptr,或者 (c) 编写自己的 RAII 句柄类来保存 FILE*。
    • boost 有一个 tr1 版本。为什么不将其添加到您的系统中。 shared_ptr 只是头文件,所以不需要在 boost 中构建任何东西,只需使用头文件即可。
    【解决方案2】:

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

    所以我可以简单地保留一个queue&lt;ScopeGuard&gt;,它会起作用:

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

    顺便谢谢你上面的回答。它以不同的方式为我提供了丰富的信息和教育。

    【讨论】:

    • 是的。它的复制构造函数“移动”,这使得它也像 auto_ptr 一样不安全。对于 C++0xified 版本,请查看我的博客文章:pizer.wordpress.com/2008/11/22/scope-guards-revisited-c0x-style
    • 别这样!这是错的。这不好。它行不通。他们在那篇 DDJ 文章中显示的保护对象与 auto_ptr 一样糟糕。它在副本上移动。这不是值类型的行为方式!
    • 另外,如果我没记错的话,ScopeGuard 只是“ScopeGuardBase const&”的类型定义。只需使用 shared_ptr 来处理 FILE 指针! ;-)
    • sellibitze,我并没有真正处理 FILE 指针并且固执地拒绝使用文件流,你知道 :-) 我只是举个例子。我正在做的更像是 sendReport(OperationFinished)。
    • 我会尽可能切换到c++0x;我仍在使用 MSVC++9。为什么你认为它们不安全? auto_ptrs 可能具有指针的语义,因此具有误导性,但这种范围保护业务是一种非常特殊的语言扩展:您并不真正期望它表现得像文明的值类​​型。实际上,如果不仔细检查它的代码,就指望它有任何东西,那你就是个傻瓜。
    猜你喜欢
    • 2019-10-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-01-06
    • 1970-01-01
    相关资源
    最近更新 更多