2009-10-14 59 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