2008-12-21 3 views
6

Bjarne Stroustrup उसकी C++ Style and Technique FAQ, जोर खान में लिखते हैं:क्या ऐसे मामले हैं जहां सी ++ में "आखिरकार" निर्माण उपयोगी होगा?

क्योंकि सी ++ एक विकल्प है कि समर्थन करता है लगभग हमेशा बेहतर: तकनीक (टीसी ++ PL3 खंड 14.4) "संसाधन अधिग्रहण प्रारंभ है।" मूल उद्देश्य किसी स्थानीय ऑब्जेक्ट द्वारा संसाधन का प्रतिनिधित्व करना है, ताकि स्थानीय ऑब्जेक्ट का विनाशक संसाधन को छोड़ देगा। इस तरह, प्रोग्रामर संसाधन को छोड़ना नहीं भूल सकता है। उदाहरण के लिए:

class File_handle { 
    FILE* p; 
public: 
    File_handle(const char* n, const char* a) 
     { p = fopen(n,a); if (p==0) throw Open_error(errno); } 
    File_handle(FILE* pp) 
     { p = pp; if (p==0) throw Open_error(errno); } 

    ~File_handle() { fclose(p); } 

    operator FILE*() { return p; } 

    // ... 
}; 

void f(const char* fn) 
{ 
    File_handle f(fn,"rw"); // open fn for reading and writing 
    // use file through f 
} 

एक सिस्टम में, हमें प्रत्येक संसाधन के लिए "संसाधन हैंडल" कक्षा की आवश्यकता होती है। हालांकि, हमारे पास संसाधन के प्रत्येक अधिग्रहण के लिए "आखिरकार" खंड नहीं होना चाहिए। यथार्थवादी प्रणालियों में, संसाधनों के प्रकार की तुलना में कहीं अधिक संसाधन अधिग्रहण होते हैं, इसलिए "संसाधन अधिग्रहण प्रारंभ होता है" तकनीक "आखिरकार" निर्माण के उपयोग से कम कोड की ओर ले जाती है।

ध्यान दें कि बजेर्न लिखते हैं "लगभग हमेशा बेहतर" और "हमेशा बेहतर" नहीं। अब मेरे प्रश्न के लिए: finally निर्माण सी ++ में वैकल्पिक निर्माण (आरएआईआई) का उपयोग करने से बेहतर होगा?

+0

आप दो अलग-अलग प्रश्न पूछ रहे हैं। शीर्षक पूछता है कि आखिरकार क्यों नहीं है, और वास्तविक पोस्ट उन मामलों के उदाहरण मांगता है जहां अंततः आरएआईआई से बेहतर होगा। मेरा सुझाव है कि आप उनमें से एक को संपादित करें, इसलिए वे वास्तव में मेल खाते हैं। यह उन लोगों के लिए भ्रमित है जो इस पोस्ट पर एक खोज में ठोकर खा रहे हैं। – jalf

उत्तर

6

एकमात्र कारण मैं सोच सकता हूं कि आखिरकार ब्लॉक "बेहतर" होगा जब एक ही चीज़ को पूरा करने के लिए कम कोड लेता है। उदाहरण के लिए, यदि आपके पास कोई संसाधन है, तो किसी कारण से आरआईआईए का उपयोग नहीं होता है, तो आपको संसाधन को लपेटने और इसे विनाशक में मुक्त करने के लिए कक्षा लिखने की आवश्यकता होगी, या अंत में ब्लॉक (यदि यह अस्तित्व में है) का उपयोग करें।

की तुलना करें:

class RAII_Wrapper 
{ 
    Resource *resource; 

public: 
    RAII_Wrapper() : resource(aquire_resource()) {} 

    ~RAII_Wrapper() { 
     free_resource(resource); 
     delete resource; 
    } 

    Resource *getResource() const { 
     return resource; 
    } 
}; 

void Process() 
{ 
    RAII_Resource wrapper; 
    do_something(wrapper.resource); 
} 

बनाम:

void Process() 
{ 
    try { 
     Resource *resource = aquire_resource(); 
     do_something(resource); 
    } 
    finally { 
     free_resource(resource); 
     delete resource; 
    } 
} 

(मुझे सहित) अधिकांश लोगों को अभी भी तर्क था कि पहले संस्करण में बेहतर है, क्योंकि यह कोशिश उपयोग करने के लिए आप के लिए मजबूर नहीं है। .. अंत में ब्लॉक करें। संसाधन को उपयोग करने वाले प्रत्येक फ़ंक्शन में कोड को डुप्लिकेट करने के लिए आपको केवल कक्षा को लिखने की आवश्यकता होती है।

संपादित करें: जैसा कि उल्लेख किया गया है, आपको पॉइंटर्स को मैन्युअल रूप से हटाने के बजाय एक auto_ptr का उपयोग करना चाहिए, जो दोनों मामलों को सरल बना देगा।

+0

मामूली टाइपो। RIIA_Resource wrapper होना चाहिए; RIIA_Resource wrapper() के बजाय; । आप auto_ptr const p (aquire_resource()) कर सकते हैं; auto_ptr सभी चीजों के बारे में परवाह करता है। मैं समझता हूं कि यह सिर्फ एक उदाहरण था :) इसलिए जब आप टाइपो को ठीक करते हैं और एक स्मार्ट पॉइंटर का उल्लेख करते हैं, तो आपको +1 मिल जाता है :) –

+0

ओह। जब मैंने इसे लिखना शुरू किया, तो मैंने संसाधन को निर्माता में पास कर दिया और इसे एक सूचक के बजाय संदर्भ में संग्रहीत किया गया था। अच्छी पकड़। –

+2

आरआईआईए नहीं, आरआईआईए होना चाहिए। – dalle

6

सी कोड से कनेक्ट करते समय अंत में बेहतर होगा। आरएआईआई में मौजूदा सी कार्यक्षमता को लपेटना एक दर्द हो सकता है।

7

उनके बीच का अंतर यह है कि विनाशक सफाई प्रकार के पुन: उपयोग को उस प्रकार के साथ जोड़कर जोर देते हैं, जबकि प्रयास/आखिरकार एक-ऑफ क्लीनअप दिनचर्या पर जोर देता है। तो कोशिश करें/आखिरकार अधिक तत्काल सुविधाजनक है जब आपके पास उपयोग किए जाने वाले एक प्रकार से जुड़े एक पुन: प्रयोज्य सफाई समाधान के बजाय, उपयोग के बिंदु से जुड़े एक अद्वितीय एक-ऑफ क्लीनअप आवश्यकता है।

मैंने यह कोशिश नहीं की है (महीनों के लिए हाल ही में जीसीसी डाउनलोड नहीं किया है), लेकिन यह सच होना चाहिए: लैम्बडास को भाषा में जोड़ने के साथ, सी ++ अब finally के प्रभावी बराबर हो सकता है, बस लिखकर एक समारोह जिसे try_finally कहा जाता है। स्पष्ट उपयोग:

try_finally([] 
{ 
    // attempt to do things in here, perhaps throwing... 
}, 
[] 
{ 
    // this always runs, even if the above block throws... 
} 
बेशक

, आप try_ finally लिखने के लिए है, लेकिन केवल एक बार और फिर आप के लिए तैयार हैं। Lambdas नए नियंत्रण संरचनाओं को सक्षम बनाता है।

कुछ की तरह:

template <class TTry, class TFinally> 
void try_finally(const TTry &tr, const TFinally &fi) 
{ 
    try 
    { 
     tr(); 
    } 
    catch (...) 
    { 
     fi(); 
     throw; 
    } 

    fi(); 
} 

और कोई लिंक बिल्कुल एक जीसी की उपस्थिति और विनाशकर्ता कोशिश अंततः बजाय/के लिए एक प्राथमिकता के बीच है। सी ++/सीएलआई में विनाशक और जीसी है। वे ऑर्थोगोनल विकल्प हैं। कोशिश करें/आखिरकार और विनाशक एक ही समस्या के लिए थोड़ा अलग समाधान हैं, दोनों निर्धारक, गैर-कवक संसाधनों के लिए आवश्यक हैं।

सी ++ फ़ंक्शन ऑब्जेक्ट पुन: प्रयोज्यता पर जोर देते हैं लेकिन एक-ऑफ अज्ञात फ़ंक्शंस को दर्दनाक बनाते हैं। लैम्बडास जोड़कर, अनाम कोड ब्लॉक अब बनाना आसान है, और यह नामित प्रकारों के माध्यम से व्यक्त "मजबूर पुन: प्रयोज्यता" पर सी ++ के पारंपरिक जोर से बचाता है।

+0

अंततः भाषा निर्माण के बिना कार्यान्वित करने का अच्छा विचार। लेकिन कोशिश करें- अंतिम रूप से फाई() को कॉल करना चाहिए भले ही tr() फेंक न जाए। – dalle

+0

कुछ पाठ को शीर्ष पर भी ले जाया गया है, इसलिए यह वास्तव में प्रश्न का उत्तर भी देता है। –

0

छः उत्तरों के बाद संपादित करें।

क्या इस एक के बारे में:

class Exception : public Exception { public: virtual bool isException() { return true; } }; 
class NoException : public Exception { public: bool isException() { return false; } }; 


Object *myObject = 0; 

try 
{ 
    try 
    { 
    myObject = new Object(); // Create an object (Might throw exception) 
    } 
    catch (Exception &e) 
    { 
    // Do something with exception (Might throw if unhandled) 
    } 

    throw NoException(); 
} 
catch (Exception &e) 
{ 
    delete myObject; 

    if (e.isException()) throw e; 
} 
+0

यदि आपकी पकड़ अभिव्यक्ति एक और अपवाद फेंकता है तो हटाएं कभी नहीं कहा जाएगा। – Kibbee

+0

ऑब्जेक्ट * myObject = 0; { myObject = new Object(); // फेंक अपवाद } पकड़ (अपवाद और ई) { कोशिश {// अपवाद के साथ कुछ करो} पकड़ {} } myObject हटाना; समस्या हल हो गई :-) –

+0

क्योंकि, ज्यादातर मामलों में, आप अपवाद को पकड़ना नहीं चाहते हैं। और यदि आप इसे पकड़ते हैं, तो आपको लगभग हमेशा इसे पुनर्स्थापित करना चाहिए। – Roddy

3

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

1

मुख्य उपयोग जो मुझे finally के लिए मिलेगा, सी कोड से निपटने के दौरान होगा, क्योंकि अन्य लोगों ने बताया कि सी संसाधन केवल कोड में एक या दो बार उपयोग किया जा सकता है और वास्तव में एक RAII- अनुरूप संरचना में लपेटने के लायक नहीं है। उस ने कहा, लैम्बडास के साथ, यह फ़ंक्शन में निर्दिष्ट फ़ंक्शन ऑब्जेक्ट का आह्वान करने वाले एक डॉटोर के माध्यम से कुछ कस्टम तर्क को केवल इतना आसान लगता है।

अन्य उपयोग केस जो मुझे मिलेगा वह विदेशी विविध कोड के लिए है, चाहे हम एक सामान्य या असाधारण निष्पादन पथ में हों, भले ही किसी टाइमस्टैम्प को मुद्रित करने या फ़ंक्शन से बाहर निकलने के बारे में कुछ भी न हो। यह इतना दुर्लभ मामला है कि मेरे लिए यह लगता है कि यह सिर्फ इसके लिए एक भाषा सुविधा रखने के लिए अधिक है, और अब इस उद्देश्य के लिए एक अलग वर्ग लिखने के बिना लैम्बदास के साथ अभी भी इतना आसान है।

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

जो स्कोप गार्ड को सरल बना देगा, अब commit/dismiss को स्कोप गार्ड नष्ट होने पर स्वचालित रूप से वापस रोल किए बिना परिवर्तन स्वीकार करने के लिए कॉल करने की आवश्यकता होगी।

ScopeGuard guard(...); 

// Cause external side effects. 
... 

// If we managed to reach this point without facing an exception, 
// dismiss/commit the changes so that the guard won't undo them 
// on destruction. 
guard.dismiss(); 

बस इस बनने के लिए: विचार यह अनुमति है

ScopeGuard guard(...); 

// Cause external side effects. 
... 

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

यह सबसे मामूली बात है लेकिन अपवाद के सबसे कठिन क्षेत्र में सुरक्षा प्राप्त करने के लिए सुरक्षा: बाहरी दुष्प्रभावों को वापस रोल करना। जब स्थानीय संसाधनों को ठीक से नष्ट करने की बात आती है तो मैं सी ++ से अधिक नहीं पूछ सकता था। यह उस उद्देश्य के लिए पहले से ही काफी आदर्श है। लेकिन बाहरी साइड इफेक्ट्स को वापस लेना हमेशा किसी भी भाषा में मुश्किल होता है जो उन्हें पहले स्थान पर होने की अनुमति देता है, और इस तरह की आसान बनाने के लिए किसी भी छोटी सी छोटी मदद से मैं हमेशा सराहना करता हूं।

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

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