मुझे C ++ में अमूर्त वर्ग के लिए आभासी विध्वंसक क्यों घोषित करना चाहिए?


165

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

जवाबों:


196

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

उदाहरण के लिए

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
delete pअपरिभाषित व्यवहार करता है। यह कॉल करने की गारंटी नहीं है Interface::~Interface
मकरसे

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

14
@Wallacoloo: यह वजह से अपरिभाषित है [expr.delete]/: ... if the static type of the object to be deleted is different from its dynamic type, ... the static type shall have a virtual destructor or the behavior is undefined. ...। यह अभी भी अपरिभाषित होगा यदि व्युत्पन्न ने एक अंतर्निहित उत्पन्न विध्वंसक का उपयोग किया।
मँकारसे

37

आपके प्रश्न का उत्तर अक्सर होता है, लेकिन हमेशा नहीं। यदि आपका सार वर्ग क्लाइंट को एक पॉइंटर को डिलीट करने के लिए मना करता है (या यदि वह इसके प्रलेखन में ऐसा कहता है), तो आप वर्चुअल डिस्ट्रक्टर घोषित नहीं करने के लिए स्वतंत्र हैं।

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

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

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


आपके उत्तर को बनाने का कार्य "जिस पर डिलीट नहीं है" को कहा जाता है। आमतौर पर अगर आपके पास एक सार आधार वर्ग है जिसे एक इंटरफ़ेस के रूप में तैयार किया गया है, तो इंटरफ़ेस वर्ग को हटाने को कहा जाएगा।
जॉन डिब्लिंग

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

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

मैंने अपने उत्तर के खराब शब्दों को अब ठीक कर दिया। यह अब स्पष्ट रूप से बताता है कि यह ग्राहकों पर भरोसा नहीं करता है। वास्तव में मुझे लगा कि यह स्पष्ट है कि कुछ करने वाले ग्राहकों पर निर्भर रहना वैसे भी बाहर है। धन्यवाद :)
जोहान्स स्काउब -

2
+1 सुरक्षित विध्वंसक का उल्लेख करने के लिए, जो एक बेस क्लास के लिए एक पॉइंटर को हटाते समय गलती से गलत डिस्ट्रक्टर को कॉल करने की समस्या का दूसरा "तरीका" है।
j_random_hacker

23

मैंने कुछ शोध करने का निर्णय लिया और आपके उत्तरों को संक्षेप में बताने का प्रयास किया। निम्नलिखित प्रश्न आपको यह तय करने में मदद करेंगे कि आपको किस प्रकार के विध्वंसक की आवश्यकता है:

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

आशा है कि ये आपकी मदद करेगा।

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

संदर्भ:


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

10

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

इसलिए, आप मेमोरी लीक की संभावना को खोल रहे हैं

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

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

7

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

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

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

यदि Baseएक आभासी विध्वंसक नहीं है, तो यह बीमार है क्योंकि यह ऑब्जेक्ट को हटाने का प्रयास करेगा जैसे कि यह एक था Base *


क्या आप बढ़ावा को ठीक नहीं करना चाहते हैं :: साझा करने के लिए बढ़ावा देने के लिए शेयर्ड # पाइंटर पी (नया व्युत्पन्न); ? शायद ppl आपके जवाब को समझेगा और वोट देगा
जोहान्स शाउब - litb

संपादित करें: "ब्रैकट" को कोण कोष्ठक के रूप में दृश्यमान बनाने के लिए कुछ हिस्सों को जोड़ा गया, जैसा कि litb ने सुझाव दिया है।
j_random_hacker

@EvanTeran: मुझे यकीन नहीं है कि यह बदल गया है क्योंकि उत्तर मूल रूप से पोस्ट किया गया था (बूस्ट प्रलेखन बूस्ट.ऑड / एलोक / एलिबो / 1_52_0 / libs / smart_ptr / sared_ptr.htm से पता चलता है कि यह हो सकता है), लेकिन यह सच नहीं है इन दिनों जो shared_ptrऑब्जेक्ट को हटाने का प्रयास करेगा जैसे कि यह एक था Base *- यह उस चीज़ का प्रकार याद रखता है जिसे आपने इसे बनाया था। संदर्भित लिंक देखें, विशेष रूप से बिट जो कहता है "विध्वंसक उसी सूचक के साथ डिलीट कॉल करेगा, अपने मूल प्रकार के साथ पूर्ण, तब भी जब टी में वर्चुअल विध्वंसक नहीं है, या शून्य है।"
स्टुअर्ट गोलोडेट

@StuartGolodetz: हम्म, आप सही हो सकते हैं, लेकिन मैं ईमानदारी से निश्चित नहीं हूं। वर्चुअल डिस्ट्रक्टर की कमी के कारण यह इस संदर्भ में अभी भी बीमार हो सकता है । यह देखने लायक है।
इवान टेरान

@EvanTeran: इस मामले में यह सहायक है - stackoverflow.com/questions/3899790/sared-ptr-magic
स्टुअर्ट गोलोडेट्ज

5

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

  1. C ++ में पदानुक्रम के अधिकांश वर्ग में वर्चुअल विध्वंसक होना चाहिए

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

Animal* pAnimal = GetAnimal();
delete pAnimal;

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

बेस क्लास में विध्वंसक को आभासी बनाने का कारण यह है कि यह केवल व्युत्पन्न वर्गों से विकल्प को हटाता है। उनका विध्वंसक डिफ़ॉल्ट रूप से आभासी हो जाता है।


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

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

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

@j_random_hacker, ब्लॉग पोस्ट के साथ जवाब देने के लिए खेद है, लेकिन यह वास्तव में इस परिदृश्य में फिट बैठता है। blogs.msdn.com/jaredpar/archive/2008/03/24/...
JaredPar

@JaredPar: बहुत बढ़िया पोस्ट, मैं आपसे 100% सहमत हूँ, खासकर रिटेल कोड में कॉन्ट्रैक्ट चेक करने के बारे में। मेरा मतलब है कि ऐसे मामले हैं जब आप जानते हैं कि आपको वर्चुअल डोर की आवश्यकता नहीं है। उदाहरण: टेम्प्लेट प्रेषण के लिए टैग कक्षाएं। उनके पास 0 आकार है, आप केवल विशेषज्ञता इंगित करने के लिए वंशानुक्रम का उपयोग करते हैं।
j_random_hacker

3

उत्तर सरल है, आपको इसे आभासी होने की आवश्यकता है अन्यथा आधार वर्ग एक पूर्ण बहुरूपी वर्ग नहीं होगा।

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

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

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