2012-06-11 12 views
9

मान लीजिए के लिए पैटर्न मैं दो कार्यों DoTaskA और DoTaskB — उनकी संगत "रोलबैक" कार्यों UndoTaskA और UndoTaskB साथ TaskException — फेंकने के दोनों सक्षम है। उपयोग करने के लिए सबसे अच्छा पैटर्न क्या है ताकि दोनों सफल हो या दोनों विफल हो जाएं?सी ++ लेन-देन की तरह सभी या कुछ भी नहीं काम

सबसे अच्छा मैं अब

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 मामले में हम एक तिहाई या बाद में एक चौथाई कार्य जोड़ने के कोड समरूपता को दिखाने के लिए अनावश्यक, लेकिन शायद अच्छा है।

सहायक बूलियन चर के कारण इस कोड को पसंद नहीं करते हैं। शायद नए सी ++ 11 में कुछ ऐसा है जो मुझे पता नहीं है, जो इसे और अधिक अच्छी तरह से कोड कर सकता है?

+0

यदि आप कैच ब्लॉक के अंदर हैं, तो 'is_task_b_done' हमेशा' false' – SuperSaiyan

+0

है, मुझे पता है, मैंने टिप्पणी जोड़ दी है कि अगर हम तीसरे या चौथे कार्य को जोड़ते हैं तो कोड समरूपता के लिए केवल यही है। – kirakun

+0

क्या आपका ऐप बहु-थ्रेडेड है? क्या आपने [RAII] (http://en.wikipedia.org/wiki/Resource_Acquisition_Is_Initialization) पर एक नज़र डाली? – dirkgently

उत्तर

12

एक छोटी सी आरए II प्रतिबद्ध/रोलबैक गुंजाइश गार्ड इस प्रकार दिखाई देंगे:

#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; 
    } 
} 

पी एस।एनन मेल कहता है, उन सभी टास्कएक्स ऑब्जेक्ट्स को एक कंटेनर में धक्का देना बेहतर है यदि आपके पास उनमें से कई हैं, तो कंटेनर को समान अर्थशास्त्र (कंटेनर पर कॉल करने के लिए प्रत्येक स्वामित्व वाली गार्ड ऑब्जेक्ट को प्रतिबद्ध करें) दें।


पीपीएस। सिद्धांत रूप में, आप स्पष्ट रूप से करने के बजाय RAII dtor में std::uncaught_exception का उपयोग कर सकते हैं। मैं स्पष्ट रूप से यहां प्रतिबद्ध होना पसंद करता हूं क्योंकि मुझे लगता है कि यह स्पष्ट है, और अपवाद के बजाय return FAILURE_CODE के साथ स्कोप से बाहर निकलने पर भी सही तरीके से काम करता है।

+0

साफ जवाब। मुझे यह टिप्पणी पसंद है कि 'std :: uncaught_exception' का उपयोग' प्रतिबद्ध 'से बचने के लिए किया जा सकता है जो मुख्य शरीर प्रवाह से कुछ हद तक अलग हो जाता है, जिससे इसे भूलना आसान हो जाता है। हमें किसी भी तरह मिश्रित त्रुटि-हैंडलिंग प्रतिमानों का कभी भी उपयोग नहीं करना चाहिए। – kirakun

0

स्केलेबिलिटी के लिए, आप इस तथ्य को सहेजना चाहते हैं कि आपको किसी कंटेनर में किसी कार्य के लिए पूर्ववत करने की आवश्यकता है। फिर, कैच ब्लॉक में, आप कंटेनर में दर्ज सभी अंडो का आह्वान करते हैं।

कंटेनर, उदाहरण के लिए, सफलतापूर्वक पूरा किए गए कार्य को पूर्ववत करने के लिए फ़ंक्शन ऑब्जेक्ट्स कर सकता है।

0

क्या आपने कमांडपटर के बारे में सोचा है? Command Pattern description

आप बोनस के साथ सभी डेटा है कि एक आदेश वर्ग की एक वस्तु में DoTaskA() क्या करता है क्या करने की जरूरत है संपुटित, यह है कि आप उल्टा कर सकते हैं यह सब, की जरूरत है जहां (एक है करने के लिए इस प्रकार की कोई जरूरत नहीं निष्पादित करने में विफल होने पर विशेष पूर्ववत करें)। कमांड पैटर्न "सभी या कुछ भी नहीं" स्थितियों को संभालने के लिए विशेष रूप से अच्छा है।

आप एक से अधिक आदेशों जो, एक दूसरे पर निर्माण अपने उदाहरण के रूप में पढ़ा जा सकता है, तो आप की जाँच करनी चाहिए chain of responsibility

शायद एक रिएक्टर पैटर्न में काम (reactor description here) आ सकता है इस के प्रवाह को विपरीत होगा नियंत्रण, लेकिन यह प्राकृतिक लगता है और आपके सिस्टम को एक मजबूत मल्टीथ्रेडेड, मल्टीकंपोनेंट डिज़ाइन में बदलने का लाभ है। लेकिन यह उदाहरण से बताना मुश्किल हो सकता है।

7

सी ++ में लेनदेन स्थिरता प्राप्त करना मुश्किल है। डॉ। डॉब के जर्नल में ScopeGuard पैटर्न का उपयोग करके वर्णित एक अच्छी विधि है। दृष्टिकोण की सुंदरता यह है कि यह सामान्य परिस्थितियों और अपवाद परिदृश्य दोनों में सफाई करता है। यह इस तथ्य का उपयोग करता है कि ऑब्जेक्ट विनाशकों को किसी भी दायरे से बाहर निकलने के लिए सुनिश्चित किया जाता है और अपवाद मामला सिर्फ एक और गुंजाइश निकास है।

+0

शायद आप कोड इनलाइन को पुन: पेश कर सकते हैं (या स्केच), अगर कोई व्यक्ति इस उत्तर को पढ़ रहा है तो डॉ। डॉब्स नीचे होने पर ही होता है? – Useless

1

यह प्राप्त करने का सबसे अच्छा तरीका स्कोप गार्ड के साथ है, मूल रूप से एक छोटा सा मुहावरे जो एक अपवाद फेंक दिया गया है तो रोलबैक हैंडलर का आह्वान करेगा।

मैंने कुछ समय पहले स्कोपगार्ड के एक सरल कार्यान्वयन के बारे में पूछा है और सवाल मेरे उत्पादन परियोजनाओं में एक अच्छा कार्यान्वयन में उपयोग किया जा रहा है। यह रोलबैक हैंडलर के रूप में सी ++ 11 और लैम्बडास के साथ काम करता है।

मेरे स्रोत में वास्तव में दो संस्करण हैं: एक जो कन्स्ट्रक्टर हैंडलर फेंकता है तो रोलबैक हैंडलर का आह्वान करेगा, और दूसरा ऐसा होता है जो ऐसा नहीं होता है।

स्रोत और उपयोग उदाहरण in here देखें।

संबंधित मुद्दे