2012-06-11 18 views
9

假設交易狀的圖案,我有兩個功能DoTaskADoTaskB —都能夠投擲TaskException —及其相應的「回滾」功能UndoTaskAUndoTaskB的。什麼是最好的使用模式,以使兩者都成功或兩者都失敗?C++的全有或全無工作

我最好現在

bool is_task_a_done = false, 
    is_task_b_done = false; 

try { 
    DoTaskA(); 
    is_task_a_done = true; 

    DoTaskB(); 
    is_task_b_done = true; 
} catch (TaskException &e) { 
    // Before rethrowing, undo any partial work. 
    if (is_task_b_done) { 
     UndoTaskB(); 
    } 
    if (is_task_a_done) { 
     UndoTaskA(); 
    } 
    throw; 
} 

我知道is_task_b_done是不必要的,但也許不錯,顯示代碼對稱的情況下,我們增加第三個或第四個任務以後。

由於輔助布爾變量,不喜歡這個代碼。也許在新的C++ 11中有一些我不知道的東西,它可以更好地編寫它?

+0

如果你在catch塊內部,'is_task_b_done'總是'false' – SuperSaiyan

+0

我知道,我已經添加了一個評論,說只有在代碼對稱的情況下才會添加第三或第四個任務。 – kirakun

+0

您的應用程序是多線程的嗎?你有沒有看過[RAII](http://en.wikipedia.org/wiki/Resource_Acquisition_Is_Initialization)? – dirkgently

回答

12

小RAII提交/回滾範圍後衛可能是這樣的:

#include <utility> 
#include <functional> 

class CommitOrRollback 
{ 
    bool committed; 
    std::function<void()> rollback; 

public: 
    CommitOrRollback(std::function<void()> &&fail_handler) 
     : committed(false), 
      rollback(std::move(fail_handler)) 
    { 
    } 

    void commit() noexcept { committed = true; } 

    ~CommitOrRollback() 
    { 
     if (!committed) 
      rollback(); 
    } 
}; 

所以,我們假設我們總是建立保護對象的交易成功後,並只有畢竟打電話commit交易成功。

void complicated_task_a(); 
void complicated_task_b(); 

void rollback_a(); 
void rollback_b(); 

int main() 
{ 
    try { 
     complicated_task_a(); 
     // if this^throws, assume there is nothing to roll back 
     // ie, complicated_task_a is internally exception safe 
     CommitOrRollback taskA(rollback_a); 

     complicated_task_b(); 
     // if this^throws however, taskA will be destroyed and the 
     // destructor will invoke rollback_a 
     CommitOrRollback taskB(rollback_b); 


     // now we're done with everything that could throw, commit all 
     taskA.commit(); 
     taskB.commit(); 

     // when taskA and taskB go out of scope now, they won't roll back 
     return 0; 
    } catch(...) { 
     return 1; 
    } 
} 

PS。因爲匿名郵件說,最好將所有這些對象都放入一個容器,如果你有很多這樣的容器,給容器提供相同的語義(在容器上調用提交來讓它提交每個擁有的防護對象)。


PPS。原則上,您可以在RAII dtor中使用std::uncaught_exception而不是顯式提交。我更願意在這裏明確提交,因爲我認爲它更清晰,並且如果您早期退出範圍而不是異常,則可以正常工作。

+0

乾淨的答案。我喜歡'std :: uncaught_exception'可以用來避免'commit'這個與主體流程有些分離的評論,因此很容易忘記。無論如何,我們絕不應該使用混合的錯誤處理模式。 – kirakun

0

對於可伸縮性,您希望保存事實,即需要對容器中的任務執行撤消操作。然後,在catch塊中,您只需調用容器中記錄的所有undos即可。

例如,容器可以包含函數對象來撤銷已成功完成的任務。

0

你有沒有想過CommandPattern? Command Pattern description

您封裝是做什麼DoTaskA()不 在命令類的對象所需的所有數據,與獎金,你可以扭轉 這一切,如果需要的話(因此沒有必要有一如果未能執行 則執行特殊撤銷)。命令模式特別適合處理「全部或全部」情況。

如果您有建立在對方,你的榜樣 可以讀取多個命令,那麼你應該調查chain of responsibility

也許反應器模式可以派上用場(reactor description here) 這將反轉的流控制,但感覺很自然,並且具有將您的系統變爲強大的多線程多組件 設計的好處。但這可能是過度的,很難從這個例子中看出來。

7

很難在C++中實現事務一致性。在Dobb博士的期刊中使用ScopeGuard模式描述了一種很好的方法。這種方法的優點在於,這需要在正常情況下和異常情況下進行清理。它利用這樣一個事實,即確保對象析構函數調用任何範圍出口,並且異常情況只是另一個範圍出口。

+0

也許你可以重現(或草圖)代碼內聯,以防萬一有人讀這個答案時,博士多布斯恰好是關閉? – Useless

1

實現此目標的最好方法是使用範圍守護進程,基本上是一個小的RAII習慣用法,如果引發異常,它將調用回滾處理程序。

我已經問了一下ScopeGuard的一個簡單實現,問題演變成我在生產項目中使用的一個很好的實現。它適用於C++ 11和lambda作爲回滾處理程序。

我的源碼實際上有兩個版本:如果構造函數處理程序拋出時會調用回滾處理程序,如果發生這種情況,則會拋出另一個版本。

檢查源和使用示例in here