2009-10-14 67 views
4

我已阅读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将与沿着范围被关闭。我想要一个范围守卫模式,我可以轻松地排队清理操作,我确定需要在运行时。有没有这样的东西可用?

我不能将范围守护对象放到标准队列中,导致原始对象(我推送的对象)在流程中被解散。如何推送堆分配的堆栈守卫,并使用一个队列删除其在dtor上的成员?有没有人有更聪明的做法?

回答

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's blog on efficient movable value types

+0

是的,对于资源析构函数(如文件句柄)来说,RAII是最好的。我只使用范围守卫来表达那些难以表现为“资源”的事物,例如 report(OperationStart); ON_BLOCK_EXIT(report,OperationEnd); doSomething(); – Ilya 2009-10-14 12:05:03

+0

遗憾的是,我的编译器中没有TR1,所以我无法使用shared_ptr。然而,auto_ptr 的向量/队列可能正常工作(假设我是堆 - 分配我的范围守卫并将它们推送到向量/队列中)。我很想知道一些C++类将变得“可移动”(不知道这个术语)。这是用refcounts完成的吗?也许我应该让范围警卫“可移动”呢? – Ilya 2009-10-14 12:06:13

+4

auto_ptr <...>的vector/queue显然不是一个好主意,因为auto_ptrs不可复制! – 2009-10-14 12:58:19

0

咦,原来,DDJ范围后卫是 “可移动”,而不是在C++ 0x感,但是与auto_ptr可移动的意义相同:在复制过程中,新守卫“解散”了旧守卫(如auto_ptr的副本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)); 
    // ... 
} 

顺便说一句,感谢您对上述问题的答案。它以不同的方式向我提供信息和教育。

+0

是的。它的拷贝构造函数“移动”,使其与auto_ptr一样不安全。对于C++ 0xified版本,请查看我的博客文章:http://pizer.wordpress.com/2008/11/22/scope-guards-revisited-c0x-style/ – sellibitze 2009-10-14 13:53:41

+0

只需不要这样做!这是不对的。这不好。它不会工作。他们在DDJ文章中显示的警卫对象与auto_ptr一样糟糕。它移动副本。这不是一个值类型应该如何表现! – sellibitze 2009-10-14 13:59:56

+0

另外,如果我正确记得,ScopeGuard只是“ScopeGuardBase常量&”的typedef。只需使用shared_ptr来处理FILE指针即可! ;-) – sellibitze 2009-10-14 14:01:47