2008-11-07 5 views
138

मुझे पता है कि सी ++ में बेस क्लास के लिए आभासी विनाशकों की घोषणा करना एक अच्छा अभ्यास है, लेकिन क्या इंटरफेस के रूप में कार्य करने वाले अमूर्त वर्गों के लिए भी virtual विध्वंसक घोषित करना हमेशा महत्वपूर्ण है? कृपया कुछ कारण और उदाहरण क्यों प्रदान करें।मुझे सी ++ में एक अमूर्त वर्ग के लिए वर्चुअल विनाशक घोषित क्यों करना चाहिए?

उत्तर

166

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

उदाहरण

class Interface 
{ 
    virtual void doSomething() = 0; 
}; 

class Derived : public Interface 
{ 
    Derived(); 
    ~Derived() 
    { 
     // Do some important cleanup... 
    } 
}; 

void myFunc(void) 
{ 
    Interface* p = new Derived(); 
    // The behaviour of the next line is undefined. It probably 
    // calls Interface::~Interface, not Derived::~Derived 
    delete p; 
} 
+4

'हटाएं पी' अपरिभाषित व्यवहार का आह्वान करता है। यह 'इंटरफ़ेस :: ~ इंटरफ़ेस' कॉल करने की गारंटी नहीं है। – Mankarse

+0

@ मंकर्स: क्या आप समझा सकते हैं कि इसे अपरिभाषित क्यों किया जाता है? यदि व्युत्पन्न ने अपने स्वयं के विनाशक को लागू नहीं किया है, तो क्या यह अभी भी अपरिभाषित व्यवहार होगा? – Ponkadoodle

+12

@Wallacoloo: यह '[expr.delete]/':' 'के कारण अपरिभाषित है ... यदि ऑब्जेक्ट का स्थैतिक प्रकार हटाया जाना है तो इसके गतिशील प्रकार से अलग है ... स्थिर प्रकार में वर्चुअल विनाशक होगा या व्यवहार अपरिभाषित है। ... '। यदि व्युत्पन्न एक निर्विवाद रूप से उत्पन्न विनाशक का उपयोग करता है तो यह अभी भी अनिर्धारित होगा। – Mankarse

6

के लिए हाँ यह हमेशा महत्वपूर्ण है। व्युत्पन्न कक्षाएं स्मृति को आवंटित कर सकती हैं या अन्य संसाधनों के संदर्भ में हो सकती हैं जिन्हें वस्तु नष्ट होने पर साफ करने की आवश्यकता होगी। यदि आप अपने इंटरफेस/अमूर्त वर्ग वर्चुअल विनाशकों को नहीं देते हैं, तो हर बार जब आप बेस क्लास हैंडल के माध्यम से व्युत्पन्न क्लास इंस्टेंस को हटाते हैं तो आपके व्युत्पन्न वर्ग 'विनाशक को नहीं बुलाया जाएगा।

इसलिए, आप स्मृति के लिए संभावित ऊपर खोलने जा रहे लीक

class IFoo 
{ 
    public: 
    virtual void DoFoo() = 0; 
}; 

class Bar : public IFoo 
{ 
    char* dooby = NULL; 
    public: 
    virtual void DoFoo() { dooby = new char[10]; } 
    void ~Bar() { delete [] dooby; } 
}; 

IFoo* baz = new Bar(); 
baz->DoFoo(); 
delete baz; // memory leak - dooby isn't deleted 
+0

सच है, वास्तव में उस उदाहरण में, यह केवल स्मृति रिसाव नहीं हो सकता है, लेकिन संभवतः क्रैश: -/ –

+1

यह क्यों क्रैश होगा? –

5

केविन, सब से पहले, मेरी कूद हमारे पिछले चर्चा में अपनी टिप्पणी पर व्यक्तिगत रूप से नहीं लेते हैं तो कृपया, मैं इरादा नहीं था इतनी कठोर से उतरने के लिए। वैसे भी, सवाल का मुख्य जवाब।

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

उदाहरण के लिए

तो:

Base *p = new Derived; 
// use p as you see fit 
delete p; 

बीमार का गठन करता है, तो बेस, एक आभासी नाशक नहीं है, क्योंकि यह वस्तु को नष्ट करने के रूप में अगर यह एक Base * थे प्रयास करेंगे है।

+0

कोई अपराध नहीं लिया गया। मैंने सोचा कि आपने कुछ अच्छे अंक बनाए हैं और सोचा है कि एक और सीधा सवाल किसी और को समान मदद प्रदान करेगा। धन्यवाद। – Kevin

+0

कोई समस्या नहीं, यह स्टैक ओवरफ़्लो की सुंदरता है, हर किसी को छात्र और शिक्षक दोनों होने का मौका मिलता है :) –

+0

आपने कुछ अच्छे अंक बनाए हैं :) –

5

यह केवल अच्छा अभ्यास नहीं है। यह किसी भी श्रेणी पदानुक्रम के लिए नियम # 1 है।

  1. आधार सी ++ में एक पदानुक्रम के सबसे वर्ग क्यों के लिए अब एक आभासी नाशक

होना आवश्यक है। ठेठ पशु पदानुक्रम ले लो। आभासी विनाशक वर्चुअल प्रेषण के माध्यम से किसी अन्य विधि कॉल के रूप में जाते हैं। निम्नलिखित उदाहरण लें।

Animal* pAnimal = GetAnimal(); 
delete pAnimal; 

मान लें कि पशु एक अमूर्त वर्ग है। सी ++ कॉल करने के लिए उचित विनाशक जानता है कि एकमात्र तरीका वर्चुअल विधि प्रेषण के माध्यम से है। यदि विनाशक आभासी नहीं है तो यह केवल पशु के विनाशक को बुलाएगा और व्युत्पन्न कक्षाओं में किसी वस्तु को नष्ट नहीं करेगा।

बेस क्लास में विनाशक वर्चुअल बनाने का कारण यह है कि यह केवल व्युत्पन्न कक्षाओं से पसंद को हटा देता है। उनका विनाशक डिफ़ॉल्ट रूप से वर्चुअल बन जाता है।

+2

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

+0

@j_random_hacker इसे संरक्षित करने से आपको गलत आंतरिक हटाए जाने से बचाएगा – JaredPar

+1

@ जेरेडपायर: यह सही है, लेकिन कम से कम आप अपने कोड में ज़िम्मेदार हो सकते हैं - कठिन बात यह सुनिश्चित करना है कि * क्लाइंट कोड * नहीं कर सकता आपके कोड को विस्फोट करने का कारण बनता है। (इसी तरह, डेटा सदस्य निजी बनाना आंतरिक सदस्य को उस सदस्य के साथ कुछ बेवकूफ करने से नहीं रोकता है।) –

34

आपके प्रश्न का उत्तर अक्सर होता है, लेकिन हमेशा नहीं। यदि आपकी अमूर्त कक्षा क्लाइंट को पॉइंटर पर हटाने के लिए मना करती है (या यदि यह इसके दस्तावेज़ में ऐसा कहती है), तो आप आभासी विनाशक घोषित नहीं कर सकते हैं।

आप ग्राहकों को इसके विनाशक को संरक्षित करके इसे पॉइंटर पर हटाने के लिए मना कर सकते हैं। इस तरह काम करना, आभासी विनाशक को छोड़ना पूरी तरह से सुरक्षित और उचित है।

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

[इस लेख में आइटम 4 देखें: http://www.gotw.ca/publications/mill18.htm]

+0

के साथ संघर्ष करता हूं, आपका उत्तर कार्य करने की कुंजी "जिस पर हटाई नहीं जाती है।" आमतौर पर यदि आपके पास एक इंटरफेस बनने के लिए डिज़ाइन किया गया एक सार बेस क्लास है, तो इंटरफ़ेस क्लास पर डिलीट होने जा रहा है। –

+0

जैसा ऊपर बताया गया जॉन, आप जो सुझाव दे रहे हैं वह बहुत खतरनाक है।आप इस धारणा पर भरोसा कर रहे हैं कि आपके इंटरफ़ेस के ग्राहक कभी भी मूल प्रकार को जानने वाले ऑब्जेक्ट को नष्ट नहीं करेंगे। एकमात्र तरीका आप गारंटी दे सकते हैं कि अगर यह गैर-वर्चुअल है तो अमूर्त वर्ग के डीटीआर को सुरक्षित बनाना है। – Michel

+0

मिशेल, मैंने ऐसा कहा है :) "यदि आप ऐसा करते हैं, तो आप अपने विनाशक को सुरक्षित बनाते हैं। यदि आप ऐसा करते हैं, तो ग्राहक उस इंटरफ़ेस में पॉइंटर का उपयोग करके हटा नहीं पाएंगे।" और वास्तव में यह ग्राहकों पर भरोसा नहीं कर रहा है, लेकिन इसे ग्राहकों को यह बताते हुए "आप नहीं कर सकते ..." को लागू करना है। मुझे कोई खतरा नहीं दिख रहा है –

20

मैं कुछ शोध करते हैं और अपने जवाब संक्षेप में प्रस्तुत करने की कोशिश करने का फैसला किया।

  1. अपनी कक्षा एक आधार वर्ग के रूप में इस्तेमाल किया जा करने का इरादा है: निम्न सवालों आप तय करने के लिए नाशक कि आप किस तरह की जरूरत में मदद मिलेगी?
    • नहीं: सार्वजनिक गैर आभासी नाशक घोषित वर्ग * से प्रत्येक वस्तु पर वी-सूचक से बचने के लिए।
    • हां: अगला प्रश्न पढ़ें।
  2. क्या आपका बेस क्लास सार है? (अर्थात किसी भी आभासी शुद्ध तरीकों?)
    • नहीं: पढ़ें अगले प्रश्न: अपने वर्ग पदानुक्रम
    • हाँ नया स्वरूप देकर अपने आधार वर्ग सार बनाने की कोशिश करें।
  3. क्या आप बेस पॉइंटर के माध्यम से पॉलिमॉर्फिक हटाना चाहते हैं?
    • नहीं: अवांछित उपयोग को रोकने के लिए संरक्षित आभासी विनाशक घोषित करें।
    • हां: सार्वजनिक वर्चुअल विनाशक घोषित करें (इस मामले में कोई ओवरहेड नहीं)।

मुझे आशा है कि इस मदद करता है।

* बात पर ध्यान देना C++ अंतिम (यानी गैर subclassable) के रूप में एक वर्ग को चिह्नित करने का कोई तरीका नहीं है कि वहाँ है, तो मामला है कि आप अपने नाशक गैर आभासी और सार्वजनिक घोषणा करने के लिए तय है, के लिए याद महत्वपूर्ण है स्पष्ट रूप से अपने साथी प्रोग्रामर को अपनी कक्षा से प्राप्त करने के खिलाफ चेतावनी दें।

संदर्भ:

+9

यह उत्तर आंशिक रूप से पुराना है, अब C++ में अंतिम कीवर्ड है। –

3

उत्तर सरल है, आपको इसे आभासी होने की आवश्यकता है अन्यथा बेस क्लास पूरी पॉलिमॉर्फिक कक्षा नहीं होगी।

Base *ptr = new Derived(); 
    delete ptr; // Here the call order of destructors: first Derived then Base. 

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