एक विध्वंसक से अपवाद फेंकना


257

अधिकांश लोग कहते हैं कि एक अपवाद को कभी भी किसी विध्वंसक से बाहर नहीं फेंकना चाहिए - ऐसा करने से अपरिभाषित व्यवहार होता है। स्ट्रॉस्ट्रुप इस बात को स्पष्ट करता है कि "वेक्टर विध्वंसक स्पष्ट रूप से हर तत्व के लिए विध्वंसक को आमंत्रित करता है। इसका मतलब है कि यदि एक तत्व विध्वंसक फेंकता है, तो वेक्टर विनाश विफल हो जाता है ... विनाशकारियों द्वारा फेंके गए अपवादों से बचाने के लिए वास्तव में कोई अच्छा तरीका नहीं है, इसलिए पुस्तकालय। यदि कोई तत्व विध्वंसक फेंकता है तो कोई गारंटी नहीं देता है "(परिशिष्ट E3.2 से)

यह लेख अन्यथा कहता प्रतीत होता है - कि विध्वंसक फेंकने वाले कमोबेश ठीक हैं।

तो मेरा प्रश्न यह है - यदि विध्वंसक से फेंकने से अपरिभाषित व्यवहार होता है, तो आप एक विध्वंसक के दौरान होने वाली त्रुटियों को कैसे संभालते हैं?

यदि एक सफाई ऑपरेशन के दौरान कोई त्रुटि होती है, तो क्या आप इसे अनदेखा करते हैं? यदि यह एक त्रुटि है जो संभावित रूप से स्टैक को संभाला जा सकता है, लेकिन विध्वंसक में सही नहीं है, तो क्या इसका मतलब यह नहीं है कि वह विध्वंसक से बाहर फेंक सकता है?

स्पष्ट रूप से इस प्रकार की त्रुटियां दुर्लभ हैं, लेकिन संभव है।


36
"एक बार में दो अपवाद" एक शेयर जवाब है लेकिन यह वास्तविक कारण नहीं है। वास्तविक कारण यह है कि एक अपवाद को फेंक दिया जाना चाहिए अगर और केवल अगर एक फ़ंक्शन के पोस्टकंडिशन पूरे नहीं किए जा सकते हैं। एक विध्वंसक का पदावनति यह है कि वस्तु अब मौजूद नहीं है। ऐसा नहीं हो सकता। किसी भी विफलता-रहित अंत-जीवन संचालन को इसलिए एक अलग विधि के रूप में कहा जाना चाहिए इससे पहले कि ऑब्जेक्ट गुंजाइश से बाहर हो जाता है (समझदार कार्य सामान्य रूप से केवल एक ही सफलता का रास्ता है)।
sprff

29
@spraff: क्या आप जानते हैं कि आपने जो कहा है वह "आरएआई को दूर फेंक" है?
कोस

16
@spraff: "ऑब्जेक्ट को कार्यक्षेत्र से बाहर जाने से पहले एक अलग विधि" कहलाने के लिए (जैसा आपने लिखा था) वास्तव में RAII फेंकता है! ऐसी वस्तुओं का उपयोग करने वाले कोड को यह सुनिश्चित करना होगा कि विध्वंसक कहे जाने से पहले ऐसी विधि को बुलाया जाएगा .. अंत में, यह विचार बिल्कुल भी मदद नहीं करता है।
दोपहर

8
@Frunsi नहीं, क्योंकि यह समस्या इस तथ्य से उपजी है कि विध्वंसक संसाधनों को जारी करने से परे कुछ करने की कोशिश कर रहा है। यह कहना आकर्षक है "मैं हमेशा XYZ करना चाहता हूं" और इस तरह के तर्क को विध्वंसक में डालने के लिए यह एक तर्क है। नहीं, आलसी मत बनो, लिखो xyz()और गैर-आरएआई तर्क के विनाशकारी को साफ रखो।
स्पैर्फ

6
@Frunsi उदाहरण के लिए, फ़ाइल के लिए कुछ करना आवश्यक नहीं है लेन-देन का प्रतिनिधित्व करने वाले वर्ग के विध्वंसक में करना ठीक है। यदि यह विफल हो गया, तो इसे संभालने में बहुत देर हो गई जब लेनदेन में शामिल सभी कोड कार्यक्षेत्र से बाहर हो गए। विध्वंसक को लेन-देन को तब तक छोड़ देना चाहिए जब तक कि कोई commit()विधि नहीं कहलाती।
निकोलस विल्सन

जवाबों:


197

एक विध्वंसक से एक अपवाद को फेंकना खतरनाक है।
यदि कोई अन्य अपवाद पहले से ही प्रचारित कर रहा है तो आवेदन समाप्त हो जाएगा।

#include <iostream>

class Bad
{
    public:
        // Added the noexcept(false) so the code keeps its original meaning.
        // Post C++11 destructors are by default `noexcept(true)` and
        // this will (by default) call terminate if an exception is
        // escapes the destructor.
        //
        // But this example is designed to show that terminate is called
        // if two exceptions are propagating at the same time.
        ~Bad() noexcept(false)
        {
            throw 1;
        }
};
class Bad2
{
    public:
        ~Bad2()
        {
            throw 1;
        }
};


int main(int argc, char* argv[])
{
    try
    {
        Bad   bad;
    }
    catch(...)
    {
        std::cout << "Print This\n";
    }

    try
    {
        if (argc > 3)
        {
            Bad   bad; // This destructor will throw an exception that escapes (see above)
            throw 2;   // But having two exceptions propagating at the
                       // same time causes terminate to be called.
        }
        else
        {
            Bad2  bad; // The exception in this destructor will
                       // cause terminate to be called.
        }
    }
    catch(...)
    {
        std::cout << "Never print this\n";
    }

}

यह मूल रूप से उबलता है:

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

तब विध्वंसक इन विधियों को कॉल करके ऑब्जेक्ट को समाप्त कर देगा (यदि उपयोगकर्ता ने स्पष्ट रूप से ऐसा नहीं किया था), लेकिन किसी भी अपवाद को फेंक दिया जाता है और पकड़ा जाता है (समस्या को ठीक करने का प्रयास करने के बाद)।

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

एक उदाहरण:

std :: fstream

करीब () विधि संभावित रूप से एक अपवाद फेंक सकती है। विध्वंसक कॉल को बंद कर देता है () यदि फ़ाइल को खोल दिया गया है, लेकिन यह सुनिश्चित करता है कि कोई भी अपवाद विध्वंसक के बाहर प्रचारित नहीं करता है।

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

स्कॉट मायर्स ने अपनी पुस्तक "प्रभावी सी ++" में इस विषय के बारे में एक उत्कृष्ट लेख दिया है

संपादित करें:

जाहिरा तौर पर "अधिक प्रभावी सी ++"
आइटम 11 में भी: विनाशकों को छोड़ने से अपवादों को रोकें


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

15
मैं असहमत हूं। कार्यक्रम समाप्त करने से स्टैक खोलना बंद हो जाता है। अधिक विध्वंसक नहीं कहा जाएगा। खोले गए किसी भी संसाधन को खुला छोड़ दिया जाएगा। मुझे लगता है कि अपवाद को निगल जाना पसंदीदा विकल्प होगा।
मार्टिन

20
OS acan संसाधनों की सफाई करता है यह मालिक का बंद है। मेमोरी, फाइलहैंडल्स आदि जटिल संसाधनों के बारे में क्या है: डीबी कनेक्शन। आपके द्वारा खोले गए ISS के लिए यह अपलिंक (क्या यह स्वचालित रूप से करीबी कनेक्शन भेजने वाला है)? मुझे यकीन है कि नासा चाहता है कि आप सफाई से कनेक्शन बंद कर दें!
मार्टिन यॉर्क

7
यदि कोई एप्लिकेशन गर्भपात करके "तेजी से विफल" होने जा रहा है, तो इसे पहली जगह में अपवाद नहीं फेंकना चाहिए। यदि यह स्टैक को वापस नियंत्रण से गुजरने में विफल हो रहा है, तो इसे इस तरह से नहीं करना चाहिए जिससे प्रोग्राम को निरस्त किया जा सके। एक या दूसरे, दोनों को मत उठाओ।
टॉम

2
@LokiAstari परिवहन प्रोटोकॉल जिसे आप अंतरिक्ष यान के साथ संचार करने के लिए उपयोग कर रहे हैं, एक गिरा हुआ कनेक्शन नहीं संभाल सकता है? ठीक है ...
doug65536

54

एक विध्वंसक के बाहर फेंकने से दुर्घटना हो सकती है, क्योंकि इस विध्वंसक को "स्टैक अनइंडिंग" के हिस्से के रूप में कहा जा सकता है। स्टैक अनइंडिंग एक ऐसी प्रक्रिया है जो अपवाद को फेंकने पर होती है। इस प्रक्रिया में, सभी वस्तुओं को "कोशिश" के बाद से स्टैक में धकेल दिया गया था और जब तक अपवाद नहीं फेंका जाता, तब तक समाप्त कर दिया जाएगा -> उनके विध्वंसक कहलाएंगे। और इस प्रक्रिया के दौरान, एक और अपवाद फेंकने की अनुमति नहीं है, क्योंकि एक समय में दो अपवादों को संभालना संभव नहीं है, इस प्रकार, यह गर्भपात () को कॉल को उकसाएगा, प्रोग्राम क्रैश हो जाएगा और नियंत्रण ओएस पर वापस आ जाएगा।


1
क्या आप कृपया विस्तार से बता सकते हैं कि उपरोक्त स्थिति में गर्भपात () कैसे हुआ। इसका अर्थ है कि निष्पादन का नियंत्रण अभी भी C ++ कंपाइलर
कृष्णा ओझा

1
@ कृष्ण_ओज़ा: काफी सरल: जब भी कोई त्रुटि डाली जाती है, तो कोड जो एक त्रुटि उठाता है कुछ बिट को इंगित करता है जो बताता है कि रनटाइम सिस्टम स्टैक अनइंडिंग की प्रक्रिया में है (यानी, कुछ अन्य को संभालना throwलेकिन catchअभी तक इसके लिए एक ब्लॉक नहीं मिला है ) जिस स्थिति में (नया) अपवाद (या स्टैक अनइंडिंग को जारी रखने) को बढ़ाने के बजाय std::terminate(नहीं abort) कहा जाता है।
मार्क वैन लीउवेन

53

हमें विशिष्ट मामलों के लिए सामान्य सलाह का आंख मूंदकर पालन करने के बजाय यहां अंतर करना होगा।

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

जब हम वर्गों को दो प्रकारों में विभाजित करते हैं, तो संपूर्ण समस्या के बारे में सोचना आसान हो जाता है। एक क्लास डोर में दो अलग-अलग जिम्मेदारियां हो सकती हैं:

  • (आर) रिलीज़ शब्दार्थ (उर्फ मुक्त कि स्मृति)
  • (सी) प्रतिबद्ध शब्दार्थ ( डिस्क के लिए उर्फ फ्लश फ़ाइल)

यदि हम प्रश्न को इस तरह से देखते हैं, तो मुझे लगता है कि यह तर्क दिया जा सकता है कि (आर) शब्दार्थ को कभी भी एक डॉटर से अपवाद का कारण नहीं होना चाहिए क्योंकि ए) हम इसके बारे में कुछ भी नहीं कर सकते हैं और बी) कई मुक्त संसाधन संचालन नहीं करते हैं यहां तक ​​कि त्रुटि जाँच के लिए भी, उदाहरण के लिए ।void free(void* p);

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

यदि हम RAII मार्ग का अनुसरण करते हैं और उन वस्तुओं के लिए अनुमति देते हैं जिनके पास (C) शब्दार्थ हैं, तो मुझे लगता है कि हमें उस विषम मामले के लिए भी अनुमति देनी होगी जहां इस तरह के मतदाता फेंक सकते हैं। यह इस प्रकार है कि आपको ऐसी वस्तुओं को कंटेनरों में नहीं रखना चाहिए और यह भी अनुसरण करता है कि terminate()यदि कोई अन्य अपवाद सक्रिय है, तो यह कार्यक्रम अभी भी कर सकता है।


त्रुटि हैंडलिंग (कमिट / रोलबैक शब्दार्थ) और अपवादों के संबंध में, एक आंद्रेई अलेक्जेंड्रेस्कु द्वारा एक अच्छी बात है : सी ++ / डिक्लेरेटिव कंट्रोल फ्लो ( एनडीसी 2014 में आयोजित ) में त्रुटि हैंडलिंग

विवरण में, वह बताते हैं कि कैसे Folly पुस्तकालय UncaughtExceptionCounterउनके ScopeGuardटूलींग के लिए लागू करता है ।

(मुझे ध्यान देना चाहिए कि दूसरों के भी समान विचार थे।)

जबकि बात एक ड्यूरेटर से फेंकने पर ध्यान केंद्रित नहीं करती है, यह एक उपकरण दिखाती है जिसका उपयोग आज उन समस्याओं से छुटकारा पाने के लिए किया जा सकता है जब एक डॉटर से फेंकने के लिए

भविष्य में , इसके लिए एक std फीचर हो सकता है, N3614 देखें , और इसके बारे में चर्चा करें

अपडेट '17: इसके लिए C ++ 17 std फीचर std::uncaught_exceptionsafaikt है। मैं जल्दी से cppref लेख उद्धृत करूँगा:

टिप्पणियाँ

एक उदाहरण जहां- intग्रेटिंग uncaught_exceptionsका उपयोग किया जाता है, वह है ... सबसे पहले एक गार्ड ऑब्जेक्ट बनाता है और इसके निर्माता में अनकैप्ड अपवादों की संख्या रिकॉर्ड करता है। आउटपुट को गार्ड ऑब्जेक्ट के डिस्ट्रक्टर द्वारा तब तक किया जाता है जब तक कि फू () फेंकता नहीं है ( जिस स्थिति में विध्वंसक में अनियोजित अपवादों की संख्या कंस्ट्रक्टर द्वारा देखी गई तुलना में अधिक है )


6
अत्यधिक सहमत हैं। और एक और शब्दार्थ (रो) रोलबैक शब्दार्थ जोड़ रहा है। आमतौर पर स्कोप गार्ड में इस्तेमाल किया जाता है। मेरे प्रोजेक्ट के मामले की तरह जहाँ मैंने ON_SCOPE_EXIT मैक्रो को परिभाषित किया। रोलबैक शब्दार्थ के बारे में मामला यह है कि यहाँ कुछ भी सार्थक हो सकता है। इसलिए हमें वास्तव में विफलता को नजरअंदाज नहीं करना चाहिए।
वेपेंग एल

मुझे लगता है कि विध्वंसकों में हमारे पास शब्दार्थ का एकमात्र कारण यह है कि C ++ समर्थन नहीं करता है finally
user541686

@Mehrdad: finally है एक dtor। यह हमेशा कहा जाता है, कोई फर्क नहीं पड़ता। अंततः सिंटैक्टिक सन्निकटन के लिए, विभिन्न स्कोप_गार्ड कार्यान्वयन देखें। आजकल, जगह में मशीनरी (मानक में भी, क्या यह C ++ 14 है?) यह पता लगाने के लिए कि क्या डोरर को फेंकने की अनुमति है, इसे पूरी तरह से सुरक्षित भी बनाया जा सकता है।
मार्टिन बा

1
@MartinBa: मुझे लगता है कि आप मेरी टिप्पणी के बिंदु से चूक गए, जो कि आश्चर्यजनक है क्योंकि मैं आपकी धारणा से सहमत था कि (R) और (C) अलग हैं। मैं यह कहना चाह रहा था कि एक डीओआर स्वाभाविक रूप से (आर) के लिए एक उपकरण है और finallyस्वाभाविक रूप से (सी) के लिए एक उपकरण है। यदि आप यह नहीं देखते हैं कि: इस बात पर विचार करें कि finallyब्लॉक में एक-दूसरे के ऊपर अपवाद फेंकना वैध क्यों है, और ऐसा ही क्यों ना हो तो अवरोधकों के लिए भी। (कुछ अर्थों में, यह एक डेटा बनाम नियंत्रण की बात है। विनाशकारी डेटा जारी करने के लिए हैं, finallyनियंत्रण जारी करने के लिए हैं। वे अलग हैं; यह दुर्भाग्यपूर्ण है कि सी ++ उन्हें एक साथ जोड़ता है।)
user541686

1
@ मेहरदाद: यहाँ बहुत देर हो रही है। यदि आप चाहें, तो आप यहां अपने तर्क दे सकते हैं: programmers.stackexchange.com/questions/304067/… । धन्यवाद।
मार्टिन बा

21

विध्वंसक से खुद को फेंकने के बारे में पूछने का असली सवाल यह है कि "कॉलर इसके साथ क्या कर सकता है?" क्या वास्तव में कुछ भी उपयोगी है जो आप अपवाद के साथ कर सकते हैं, जो एक विध्वंसक से फेंकने से उत्पन्न खतरों को दूर करेगा?

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


11
बस ध्यान दिया ... एक डोर से फेंकना कभी भी अपरिभाषित व्यवहार नहीं है । ज़रूर, इसे टर्मिनेट () कहा जा सकता है, लेकिन यह बहुत अच्छा व्यवहार है।
मार्टिन बा

4
std::ofstreamविध्वंसक फ्लश और फिर फ़ाइल बंद कर देता है। फ्लशिंग करते समय एक डिस्क पूर्ण त्रुटि हो सकती है, जिसे आप बिल्कुल उपयोगी कर सकते हैं: उपयोगकर्ता को एक त्रुटि संवाद दिखाते हुए कहे कि डिस्क खाली स्थान से बाहर है।
एंडी

13

यह खतरनाक है, लेकिन यह पठनीयता / कोड समझने की दृष्टि से भी कोई मतलब नहीं है।

आपको जो पूछना है वह इस स्थिति में है

int foo()
{
   Object o;
   // As foo exits, o's destructor is called
}

अपवाद को क्या पकड़ना चाहिए? क्या फू के कॉलर को चाहिए? या फू को संभालना चाहिए? क्यों फू के कॉलर को किसी वस्तु के बारे में ध्यान देना चाहिए आंतरिक को? भाषा को समझने के लिए इसे परिभाषित करने का एक तरीका हो सकता है, लेकिन इसका अप्राप्य और समझने में मुश्किल होना।

इससे भी महत्वपूर्ण बात, ऑब्जेक्ट के लिए मेमोरी कहाँ जाती है? स्मृति के स्वामित्व वाली वस्तु कहां जाती है? क्या यह अभी भी आवंटित किया गया है (अस्थिर रूप से क्योंकि विध्वंसक विफल हुआ)? इस बात पर भी विचार करें कि ऑब्जेक्ट स्टैक स्पेस में था , इसलिए इसकी स्पष्ट रूप से परवाह किए बिना चला गया।

फिर इस मामले पर विचार करें

class Object
{ 
   Object2 obj2;
   Object3* obj3;
   virtual ~Object()
   {
       // What should happen when this fails? How would I actually destroy this?
       delete obj3;

       // obj 2 fails to destruct when it goes out of scope, now what!?!?
       // should the exception propogate? 
   } 
};

जब obj3 का विलोपन विफल हो जाता है, तो मैं वास्तव में इस तरह से कैसे हटाऊं कि विफल न होने की गारंटी हो? इसकी मेरी स्मृति!

अब पहले कोड पर विचार करें स्निपेट ऑब्जेक्ट अपने आप ही चला जाता है क्योंकि स्टैक पर इसका ऑब्जेक्ट 3 है जबकि ढेर पर। चूंकि सूचक ऑब्जेक्ट 3 में चला गया है, आप एसओएल की तरह हैं। आपके पास स्मृति रिसाव है।

अब चीजों को करने का एक सुरक्षित तरीका निम्नलिखित है

class Socket
{
    virtual ~Socket()
    {
      try 
      {
           Close();
      }
      catch (...) 
      {
          // Why did close fail? make sure it *really* does close here
      }
    } 

};

यह भी देखें FAQ


इस उत्तर को पुनर्जीवित करते हुए, फिर से: पहला उदाहरण, के बारे में int foo(), आप एक फ़ंक्शन-कोशिश-ब्लॉक का उपयोग करके पूरे फ़ंक्शन फू को एक कोशिश-पकड़ने वाले ब्लॉक में लपेट सकते हैं, जिसमें विध्वंसक को पकड़ना शामिल है, यदि आप ऐसा करने की परवाह करते हैं। अभी भी पसंदीदा दृष्टिकोण नहीं है, लेकिन यह एक बात है।
tyree731

13

सी ++ (आईएसओ / आईईसी जेटीसी 1 / एससी 22 एन 4411) के लिए आईएसओ ड्राफ्ट से

इसलिए विध्वंसक को आम तौर पर अपवादों को पकड़ना चाहिए और उन्हें विध्वंसक से बाहर नहीं फैलने देना चाहिए।

3 एक कोशिश ब्लॉक से पथ पर निर्मित स्वचालित वस्तुओं के लिए विध्वंसक कॉल करने की प्रक्रिया को "स्टैक अनइंडिंग" कहा जाता है। [नोट: स्टैक अनइंडिंग के दौरान बुलाया एक डिस्ट्रक्टर अपवाद के साथ बाहर निकलता है, तो std :: terminate कहा जाता है (15.5.1)। इसलिए विध्वंसक को आम तौर पर अपवादों को पकड़ना चाहिए और उन्हें विध्वंसक से बाहर नहीं फैलने देना चाहिए। - अंतिम नोट]


1
सवाल का जवाब नहीं दिया - ओपी को पहले से ही इसकी जानकारी है।
अराफंगियन

2
@Arafangion मुझे संदेह है कि वह इस बारे में अवगत था (std :: समाप्त होना कहा जा रहा है) क्योंकि स्वीकृत उत्तर बिल्कुल उसी बिंदु पर बनाया गया था।
लाठर

@Arafangion कुछ जवाबों के रूप में यहाँ कुछ लोगों ने उल्लेख किया कि गर्भपात () कहा जा रहा है; या यह है कि std :: घुमाव में समाप्त गर्भपात () फ़ंक्शन को कॉल करता है।
कृष्ण ओझा

7

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


7

मैं उस समूह में हूं जो समझता है कि विध्वंसक में फेंकने वाला "स्कॉप्ड गार्ड" पैटर्न कई स्थितियों में उपयोगी है - विशेष रूप से इकाई परीक्षणों के लिए। हालांकि, ध्यान रखें कि C ++ 11 में, एक विध्वंसक परिणाम में फेंकने के std::terminateबाद से विध्वंसक परिणाम के साथ एनोटेट किया जाता है noexcept

अंद्रेज क्रेजेमीस्की के पास फेंकने वाले विनाशकों के विषय पर एक महान पोस्ट है:

वह बताते हैं कि C ++ 11 में noexceptडिस्ट्रक्टर्स के लिए डिफ़ॉल्ट को ओवरराइड करने के लिए एक तंत्र है :

C ++ 11 में, एक विध्वंसक रूप में स्पष्ट रूप से निर्दिष्ट किया गया है noexcept। यहां तक ​​कि अगर आप कोई विनिर्देश नहीं जोड़ते हैं और अपने विध्वंसक को इस तरह परिभाषित करते हैं:

  class MyType {
        public: ~MyType() { throw Exception(); }            // ...
  };

कंपाइलर अभी भी अदृश्य रूप noexceptसे आपके विनाशकर्ता के लिए विनिर्देशन जोड़ देगा । और इसका मतलब यह है कि जिस क्षण आपका विध्वंसक एक अपवाद फेंकता है, std::terminateउसे कॉल किया जाएगा, भले ही कोई डबल-अपवाद स्थिति न हो। यदि आप अपने विनाशकों को फेंकने की अनुमति देने के लिए वास्तव में दृढ़ हैं, तो आपको इसे स्पष्ट रूप से निर्दिष्ट करना होगा; आपके पास तीन विकल्प हैं:

  • स्पष्ट रूप से अपने विध्वंसक के रूप में निर्दिष्ट करें noexcept(false),
  • अपनी कक्षा को दूसरे से पहले से ही इसके विध्वंसक के रूप में निर्दिष्ट करता है noexcept(false)
  • अपनी कक्षा में एक गैर-स्थैतिक डेटा सदस्य रखें जो पहले से ही इसके विध्वंसक के रूप में निर्दिष्ट करता है noexcept(false)

अंत में, यदि आप विध्वंसक में फेंकने का निर्णय लेते हैं, तो आपको हमेशा दोहरे अपवाद के जोखिम के बारे में पता होना चाहिए (फेंकना जबकि स्टैक को अपवाद के कारण खोल दिया जा रहा है)। यह एक कॉल का कारण होगा std::terminateऔर यह शायद ही कभी आप चाहते हैं। इस व्यवहार से बचने के लिए, आप बस जाँच कर सकते हैं कि नया प्रयोग करने से पहले ही कोई अपवाद है या नहीं std::uncaught_exception()


6

बाकी सभी ने समझाया कि विध्वंसक फेंकना भयानक क्यों है ... आप इसके बारे में क्या कर सकते हैं? यदि आप एक ऑपरेशन कर रहे हैं जो विफल हो सकता है, तो एक अलग सार्वजनिक विधि बनाएं जो सफाई करता है और मनमाना अपवादों को फेंक सकता है। ज्यादातर मामलों में, उपयोगकर्ता इसे अनदेखा करेंगे। यदि उपयोगकर्ता क्लीनअप की सफलता / विफलता की निगरानी करना चाहते हैं, तो वे केवल स्पष्ट सफाई रूटीन कह सकते हैं।

उदाहरण के लिए:

class TempFile {
public:
    TempFile(); // throws if the file couldn't be created
    ~TempFile() throw(); // does nothing if close() was already called; never throws
    void close(); // throws if the file couldn't be deleted (e.g. file is open by another process)
    // the rest of the class omitted...
};

मैं एक समाधान की तलाश कर रहा हूं लेकिन वे यह समझाने की कोशिश कर रहे हैं कि क्या हुआ और क्यों हुआ। बस यह स्पष्ट करना चाहते हैं कि नजदीकी फ़ंक्शन को विध्वंसक के अंदर कहा जाता है?
जेसन लियू

5

मुख्य उत्तरों के अतिरिक्त, जो अच्छे, व्यापक और सटीक हैं, मैं आपके द्वारा संदर्भित लेख के बारे में टिप्पणी करना चाहूंगा - जो कहता है कि "विनाशकों में अपवादों को फेंकना इतना बुरा नहीं है"।

आलेख "अपवादों को फेंकने के लिए विकल्प क्या हैं" लाइन लेता है, और प्रत्येक विकल्पों में से कुछ समस्याओं को सूचीबद्ध करता है। ऐसा करने के बाद यह निष्कर्ष निकलता है कि क्योंकि हम एक समस्या मुक्त विकल्प नहीं खोज सकते हैं, हमें अपवादों को फेंकते रहना चाहिए।

मुसीबत यह है कि विकल्पों में से कोई भी समस्या यह नहीं है कि अपवाद के व्यवहार के रूप में कहीं भी बुरा हो, अपवाद के रूप में, जो याद है, "आपके कार्यक्रम का अपरिभाषित व्यवहार" है। लेखक की कुछ आपत्तियों में "सौंदर्यवादी रूप से बदसूरत" और "बुरी शैली को प्रोत्साहित करना" शामिल हैं। अब जो आप बल्कि होगा? खराब शैली वाला एक कार्यक्रम, या एक जो अपरिभाषित व्यवहार प्रदर्शित करता है?


1
अपरिभाषित व्यवहार नहीं, बल्कि तत्काल समाप्ति।
मार्क वैन लीउवेन

मानक 'अपरिभाषित व्यवहार' कहता है। यह व्यवहार अक्सर समाप्ति है, लेकिन यह हमेशा नहीं होता है।
डीजेकेवर्थ

नहीं, अपवाद हैंडलिंग में> [अपवाद को छोड़कर] पढ़ें-> विशेष कार्य (जो मानक की मेरी प्रति में 15.5.1 है, लेकिन इसकी संख्या संभवतः पुरानी है)।
मार्क वैन लीउवेन

2

प्रश्न: तो मेरा प्रश्न यह है - यदि विध्वंसक से फेंकने से अपरिभाषित व्यवहार होता है, तो आप एक विध्वंसक के दौरान होने वाली त्रुटियों को कैसे संभालते हैं?

एक: कई विकल्प हैं:

  1. अपवादों को अपने विध्वंसक से बाहर आने दें, फिर चाहे वह कहीं भी हो। और ऐसा करने में जागरूक (या यहां तक ​​कि भयभीत) हो कि एसटीडी: समाप्त हो सकता है।

  2. अपने विध्वंसक से अपवाद को कभी बाहर न आने दें। यदि आप कर सकते हैं तो एक लॉग, कुछ बड़े लाल बुरे पाठ के लिए लिखें।

  3. मेरा फव्वारा : यदि std::uncaught_exceptionझूठे लौटते हैं, तो आप अपवादों को छोड़ देते हैं। यदि यह सही है, तो लॉगिंग दृष्टिकोण पर वापस जाएं।

लेकिन क्या यह अच्छा नहीं है कि आप इसे फेंक दें?

मैं सबसे ऊपर से सहमत हूं कि फेंकना विनाशकारी में सबसे अच्छा बचा जाता है, जहां यह हो सकता है। लेकिन कभी-कभी आपको यह स्वीकार करना सबसे अच्छा लगता है कि यह हो सकता है, और इसे अच्छी तरह से संभाल लें। मैं ऊपर 3 चुनूंगा।

कुछ विषम मामले हैं, जहां वास्तव में एक विध्वंसक से फेंकने के लिए एक महान विचार है। जैसे की "check करना चाहिए" एरर कोड। यह एक मूल्य प्रकार है जो एक फ़ंक्शन से वापस किया जाता है। यदि कॉलर निहित त्रुटि कोड को पढ़ता / जांचता है, तो लौटाया गया मान चुपचाप नष्ट हो जाता है। लेकिन , यदि लौटाया गया त्रुटि कोड तब तक पढ़ा नहीं गया है, जब रिटर्न मान स्कोप के दायरे से बाहर चला जाता है, तो यह कुछ अपवाद को फेंक देगा, इसके अवरोधक से


4
आपका हौसला कुछ ऐसा है जिसे मैंने हाल ही में आज़माया है, और यह पता चला है कि आपको ऐसा नहीं करना चाहिए । gotw.ca/gotw/047.htm
GManNickG

1

मैं वर्तमान में नीति का पालन करता हूं (कि बहुत सारे कह रहे हैं) कि कक्षाएं अपने विध्वंसकों से अपवादों को सक्रिय रूप से फेंकना नहीं चाहिए, बल्कि इसके बजाय ऑपरेशन को करने के लिए एक सार्वजनिक "करीब" विधि प्रदान करनी चाहिए जो विफल हो सकती है ...

... लेकिन मेरा मानना ​​है कि कंटेनर-प्रकार की कक्षाओं के लिए विनाशकारी, एक वेक्टर की तरह, उन वर्गों से फेंके गए अपवादों को मुखौटा नहीं बनाना चाहिए जिनमें वे होते हैं। इस मामले में, मैं वास्तव में "फ्री / क्लोज़" पद्धति का उपयोग करता हूं जो खुद को पुनरावर्ती कहता है। हां, मैंने फिर से कहा। इस पागलपन के लिए एक विधि है। अपवाद प्रसार वहाँ एक स्टैक होने पर निर्भर करता है: यदि एक एकल अपवाद होता है, तो दोनों शेष विध्वंसक अभी भी चलेंगे और लंबित अपवाद नियमित रूप से वापस आने पर प्रचार करेंगे, जो बहुत अच्छा है। यदि कई अपवाद होते हैं, तो (संकलक के आधार पर) या तो पहला अपवाद प्रचारित करेगा या कार्यक्रम समाप्त हो जाएगा, जो ठीक है। यदि बहुत सारे अपवाद होते हैं, तो पुनरावृत्ति ढेर पर हावी हो जाती है, तो कुछ गंभीर रूप से गलत है, और किसी को इसके बारे में पता लगाना है, जो ठीक भी है। व्यक्तिगत रूप से,

मुद्दा यह है कि कंटेनर तटस्थ रहता है, और यह तय करने के लिए निहित वर्गों पर निर्भर है कि वे अपने विध्वंसक से अपवादों को फेंकने के संबंध में व्यवहार करते हैं या गलत व्यवहार करते हैं।


1

निर्माणकर्ताओं के विपरीत, जहां अपवादों को फेंकना यह इंगित करने का एक उपयोगी तरीका हो सकता है कि ऑब्जेक्ट निर्माण सफल रहा, अपवादों को विनाशकों में नहीं फेंकना चाहिए।

समस्या तब होती है जब स्टैक अनइंडिंग प्रक्रिया के दौरान एक विध्वंसक से एक अपवाद फेंक दिया जाता है। यदि ऐसा होता है, तो संकलक को ऐसी स्थिति में रखा जाता है, जहां यह नहीं पता होता है कि स्टैक अनइंडिंग प्रक्रिया को जारी रखना है या नए अपवाद को संभालना है या नहीं। अंतिम परिणाम यह है कि आपका कार्यक्रम तुरंत समाप्त हो जाएगा।

नतीजतन, कार्रवाई का सबसे अच्छा कोर्स पूरी तरह से विनाशकारी में अपवादों का उपयोग करने से रोकना है। इसके बजाय लॉग फ़ाइल में संदेश लिखें।


1
लॉग फ़ाइल में संदेश लिखना अपवाद का कारण बन सकता है।
कोनार्ड

1

मार्टिन बा (ऊपर) सही रास्ते पर है- आप RAILASE और COMMIT लॉजिक के लिए अलग-अलग आर्किटेक्ट हैं।

रिलीज के लिए:

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

कमिट के लिए:

यह वह जगह है जहाँ आप एक ही तरह के RAII रैपर ऑब्जेक्ट चाहते हैं, जो std :: lock_guard जैसी चीजें म्यूटेक्स के लिए प्रदान कर रहे हैं। उन लोगों के साथ आप डीटीआर एटी ऑल में प्रतिबद्ध तर्क नहीं रखते हैं। आपके पास इसके लिए एक समर्पित एपीआई है, फिर रैपर ऑब्जेक्ट्स जो RAII इसे THEIR नर्तकियों में प्रतिबद्ध करेंगे और वहां त्रुटियों को संभालेंगे। याद रखें, आप बस एक विनाशकारी में CATCH अपवादों को ठीक कर सकते हैं; इसके जारी करना उन्हें घातक है। यह आपको एक अलग आवरण (उदाहरण के लिए std :: unique_lock बनाम std: lock_guard) का निर्माण करके नीति और अलग-अलग त्रुटि से निपटने की सुविधा देता है, और यह सुनिश्चित करता है कि आप कमिटेड लॉजिक को कॉल करना न भूलें- जो केवल आधा रास्ता है 1 जगह में डोरट में डालने के लिए सभ्य औचित्य।


1

तो मेरा प्रश्न यह है - यदि विध्वंसक से फेंकने से अपरिभाषित व्यवहार होता है, तो आप एक विध्वंसक के दौरान होने वाली त्रुटियों को कैसे संभालते हैं?

मुख्य समस्या यह है: आप असफल नहीं हो सकते । आखिर असफल होने का मतलब क्या है? यदि डेटाबेस में लेन-देन करना विफल हो जाता है, और यह विफल होने में विफल रहता है (रोलबैक में विफल रहता है), तो हमारे डेटा की अखंडता का क्या होता है?

चूंकि विनाशकारी सामान्य और असाधारण (असफल) दोनों रास्तों के लिए आह्वान किया जाता है, वे स्वयं विफल नहीं हो सकते हैं या फिर हम "असफल होने के लिए असफल" हैं।

यह एक वैचारिक रूप से कठिन समस्या है, लेकिन अक्सर समाधान केवल यह सुनिश्चित करने का एक तरीका है कि विफल नहीं हो सकता। उदाहरण के लिए, एक डेटाबेस बाहरी डेटा संरचना या फ़ाइल करने से पहले परिवर्तन लिख सकता है। यदि लेनदेन विफल हो जाता है, तो फ़ाइल / डेटा संरचना को फेंक दिया जा सकता है। इसके बाद यह सुनिश्चित करना है कि उस बाहरी संरचना से परिवर्तन किए जाएं / परमाणु लेनदेन दर्ज करें जो विफल नहीं हो सकते।

व्यावहारिक समाधान शायद यह सुनिश्चित कर रहा है कि असफलता पर असफल होने की संभावना खगोलीय रूप से असंभव है, क्योंकि असफल होने के लिए चीजों को विफल करना कुछ मामलों में लगभग असंभव हो सकता है।

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

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

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

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

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

अगर कुछ भी जो इसे निर्दिष्ट करता है (वर्चुअल फ़ंक्शंस सहित जो इसके बेस क्लास के नॉइसेप्ट स्पेसिफिकेशन को इनहेरिट करना चाहिए) को फेंकने में नॉट्रो / नोएसेप्ट वास्तव में एक कंपाइलर त्रुटि में अनुवादित होता है तो यह बहुत मदद करेगा। यदि हम वास्तव में अनजाने में एक विध्वंसक लिखते हैं जो फेंक सकता है, तो इस तरह हम संकलन-समय पर यह सब सामान पकड़ सकेंगे।


1
विनाश अब विफलता है?
जिज्ञासु

मुझे लगता है कि इसका मतलब है कि उस विफलता को साफ करने के लिए, एक विफलता के दौरान विनाशकों को बुलाया जाता है। इसलिए यदि एक विनाशकारी को एक सक्रिय अपवाद के दौरान कहा जाता है, तो यह पिछली विफलता से सफाई करने में विफल है।
user2445507

0

अलार्म ईवेंट सेट करें। आमतौर पर अलार्म घटनाओं वस्तुओं को साफ करते समय विफलता को सूचित करने का बेहतर रूप है

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.