वर्चुअल डिस्ट्रक्टर्स का उपयोग कब करें?


1485

मुझे सबसे अधिक ओओ सिद्धांत की ठोस समझ है लेकिन एक चीज जो मुझे बहुत भ्रमित करती है वह है वर्चुअल डिस्ट्रक्टर्स।

मुझे लगा कि श्रृंखला में हर वस्तु के लिए विध्वंसक को हमेशा कोई बात नहीं मिलती है।

आप उन्हें आभासी बनाने के लिए कब और क्यों हैं?



146
हर विध्वंसक नीचे कोई बात नहीं कहा जाता है। virtualयह सुनिश्चित करता है कि यह मध्य के बजाय शीर्ष पर शुरू हो।
डिंग


@MingDuck कुछ भ्रामक टिप्पणी है।
यूरी पिनहोल

1
@FranklinYu यह अच्छा है कि आपने पूछा क्योंकि अब मैं उस टिप्पणी के साथ कोई मुद्दा नहीं देख सकता (सिवाय टिप्पणियों में जवाब देने की कोशिश के)।
यूरी पिनहोल

जवाबों:


1571

वर्चुअल डिस्ट्रक्टर्स तब उपयोगी होते हैं जब आप संभावित रूप से एक पॉइंटर को बेस क्लास के लिए पॉइंटर के माध्यम से हटा सकते हैं:

class Base 
{
    // some virtual methods
};

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

यहाँ, आप देखेंगे कि मैंने बेस के विध्वंसक होने की घोषणा नहीं की virtual। अब, निम्नलिखित स्निपेट पर एक नज़र डालते हैं:

Base *b = new Derived();
// use b
delete b; // Here's the problem!

के बाद से बेस नाशक नहीं है virtualऔर bएक है Base*एक करने के लिए इशारा Derivedवस्तु, delete bहै अपरिभाषित व्यवहार :

[में delete b], यदि हटाई जाने वाली वस्तु का स्थैतिक प्रकार उसके गतिशील प्रकार से भिन्न होता है, तो स्थैतिक प्रकार हटाए जाने वाली वस्तु के गतिशील प्रकार का एक आधार वर्ग होगा और स्थैतिक प्रकार में एक आभासी विध्वंसक या होगा व्यवहार अपरिभाषित है

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

योग करने के लिए, हमेशा आधार वर्ग के विध्वंसक virtualबनाते हैं जब उनका मतलब बहुरूपिए से छेड़छाड़ करना होता है।

यदि आप बेस क्लास पॉइंटर के माध्यम से इंस्टेंट को हटाने से रोकना चाहते हैं, तो आप बेस क्लास डिस्ट्रक्टर को संरक्षित और गैर-संवैधानिक बना सकते हैं; ऐसा करने से, कंपाइलर आपको deleteबेस क्लास पॉइंटर पर कॉल नहीं करने देगा ।

आप हर्ब सटर के इस लेख में वर्चुअलिटी और वर्चुअल बेस क्लास डिस्ट्रक्टर के बारे में अधिक जान सकते हैं ।


173
यह बताएगा कि मैंने पहले बनाई गई फैक्ट्री का उपयोग करके बड़े पैमाने पर लीक क्यों किया। सब अब समझ में आता है। धन्यवाद
Lodle

8
वैसे, यह एक बुरा उदाहरण है क्योंकि डेटा सदस्य नहीं हैं। क्या होगा अगर Baseऔर Derivedहै सब स्वत: भंडारण चर? यानी विध्वंसक में निष्पादित करने के लिए कोई "विशेष" या अतिरिक्त कस्टम कोड नहीं है। क्या किसी भी विध्वंसक को लिखना बंद करना ठीक है? या क्या व्युत्पन्न वर्ग में अभी भी स्मृति रिसाव होगा?
बोरोबोबो


28
हर्ब सटर के लेख से: "दिशानिर्देश # 4: एक बेस क्लास विध्वंसक या तो सार्वजनिक और आभासी होना चाहिए, या संरक्षित और गैर-धार्मिक होना चाहिए।"
सुन्दे

3
इसके अलावा लेख से - 'यदि आप एक आभासी विध्वंसक के बिना बहुरूपिए को हटाते हैं, तो आप "अपरिभाषित व्यवहार" के खूंखार दर्शक को बुलाते हैं, "एक दर्शक जो मैं व्यक्तिगत रूप से एक मामूली रूप से अच्छी तरह से जली हुई गली में नहीं मिलूंगा, आपको बहुत-बहुत धन्यवाद।' lol
बॉन्डोलिन

219

एक वर्चुअल कंस्ट्रक्टर संभव नहीं है लेकिन वर्चुअल डिस्ट्रक्टर संभव है। चलिए हम प्रयोग करते हैं ……।

#include <iostream>

using namespace std;

class Base
{
public:
    Base(){
        cout << "Base Constructor Called\n";
    }
    ~Base(){
        cout << "Base Destructor called\n";
    }
};

class Derived1: public Base
{
public:
    Derived1(){
        cout << "Derived constructor called\n";
    }
    ~Derived1(){
        cout << "Derived destructor called\n";
    }
};

int main()
{
    Base *b = new Derived1();
    delete b;
}

उपरोक्त कोड निम्न का उत्पादन करता है:

Base Constructor Called
Derived constructor called
Base Destructor called

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

#include <iostream>

using namespace std;

class Base
{ 
public:
    Base(){
        cout << "Base Constructor Called\n";
    }
    virtual ~Base(){
        cout << "Base Destructor called\n";
    }
};

class Derived1: public Base
{
public:
    Derived1(){
        cout << "Derived constructor called\n";
    }
    ~Derived1(){
        cout << "Derived destructor called\n";
    }
};

int main()
{
    Base *b = new Derived1();
    delete b;
}

आउटपुट निम्नानुसार बदल गया:

Base Constructor Called
Derived Constructor called
Derived destructor called
Base destructor called

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


1
"वर्चुअल कंस्ट्रक्टर संभव नहीं है" का अर्थ है कि आपको अपने द्वारा वर्चुअल कंस्ट्रक्टर लिखने की आवश्यकता नहीं है। व्युत्पन्न वस्तु का निर्माण व्युत्पन्न से आधार तक निर्माण की श्रृंखला का पालन करना चाहिए। इसलिए आपको अपने कंस्ट्रक्टर के लिए वर्चुअल कीवर्ड लिखने की आवश्यकता नहीं है। धन्यवाद
Tunvir रहमान Tusher

4
@Murantilism, "वर्चुअल कंस्ट्रक्टर नहीं किया जा सकता है" वास्तव में सच है। एक निर्माता को वर्चुअल चिह्नित नहीं किया जा सकता है।
cmeub

1
@ कैमेब, लेकिन एक वर्चुअल कंस्ट्रक्टर से जो आप चाहते हैं उसे प्राप्त करने के लिए एक मुहावरा है। Parashift.com/c+-faq-lite/virtual-ctors.html
cape1232

@TunvirRahmanTusher क्या आप बता सकते हैं कि बेस डिस्ट्रक्टर को क्यों कहा जाता है ??
राइमलोनफायर

@rimiro c ++ द्वारा स्वचालित। आप लिंक stackoverflow.com/questions/677620/…
तुनवीर रहमान तुषार

195

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


14
+ "यदि किसी वर्ग का कोई आभासी कार्य होता है, तो उसके पास एक आभासी विध्वंसक होना चाहिए, और यह कि आधार वर्ग के लिए डिज़ाइन नहीं किए गए हैं या बहुरूपिक रूप से उपयोग नहीं किए जाने वाले डिज़ाइनों को आभासी विध्वंसक घोषित नहीं करना चाहिए।": क्या ऐसे मामले हैं जिनमें यह समझ में आता है। इस नियम को तोड़ो यदि नहीं, तो क्या यह समझ में आएगा कि संकलक ने इस स्थिति की जाँच की और एक त्रुटि जारी की कि क्या यह संतुष्ट नहीं है?
जियोर्जियो

@ जियोर्जियो मुझे नियम के किसी अपवाद का पता नहीं है। लेकिन मैं खुद को C ++ विशेषज्ञ के रूप में रेट नहीं करूंगा, इसलिए आप इसे एक अलग प्रश्न के रूप में पोस्ट करना चाह सकते हैं। एक संकलक चेतावनी (या स्थैतिक विश्लेषण उपकरण से एक चेतावनी) मेरे लिए समझ में आता है।
छिपकली का बिल

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

3
@dascandy बिल्कुल - वह या अन्य कई स्थितियां जहां हम बहुरूपतापूर्ण व्यवहार का उपयोग करते हैं लेकिन संकेत के माध्यम से भंडारण प्रबंधन नहीं करते हैं - जैसे स्वचालित या स्थैतिक-अवधि की वस्तुओं को बनाए रखना, केवल संकेत मार्गों के रूप में उपयोग किए जाने वाले बिंदुओं के साथ। ऐसे मामलों में वर्चुअल विध्वंसक को लागू करने की कोई आवश्यकता / उद्देश्य नहीं है। चूंकि हम यहां लोगों को उद्धृत कर रहे हैं, इसलिए मैं ऊपर से Sutter पसंद करता हूं: "दिशानिर्देश # 4: एक बेस क्लास विध्वंसक सार्वजनिक या आभासी होना चाहिए, या संरक्षित और गैर-धार्मिक होना चाहिए।" उत्तरार्द्ध किसी को गलती से बेस पॉइंटर के माध्यम से हटाने की कोशिश करता है यह सुनिश्चित करता है कि उनके तरीकों की त्रुटि को दिखाया गया है
अंडरस्कोर_ड

1
@Giorgio वास्तव में एक चाल है जो एक विनाशकारी के लिए एक आभासी कॉल का उपयोग कर सकता है और उससे बच सकता है: एक कॉन्स्ट रेफरेंस के माध्यम से एक व्युत्पन्न वस्तु को आधार तक बाँध देता है, जैसे const Base& = make_Derived();। इस मामले में, Derivedप्रचलन के विध्वंसक को बुलाया जाएगा, भले ही यह आभासी न हो, इसलिए कोई vtables / vpointers द्वारा शुरू किए गए ओवरहेड को बचाता है। बेशक दायरा काफी सीमित है। आंद्रेई अलेक्जेंड्रेस्कु ने अपनी किताब मॉडर्न सी ++ डिजाइन में इसका उल्लेख किया है ।
20

46

यह भी ध्यान रखें कि जब कोई वर्चुअल डिस्ट्रक्टर नहीं है तो बेस क्लास पॉइंटर को हटाना अपरिभाषित व्यवहार का परिणाम होगा । कुछ ऐसा जो मैंने अभी हाल ही में सीखा है:

C ++ में डिलीट को ओवरराइड कैसे करना चाहिए?

मैं वर्षों से C ++ का उपयोग कर रहा हूं और मैं अभी भी खुद को लटकाता हूं।


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

बहुत ज्यादा एक ही बात है। डिफ़ॉल्ट निर्माता वर्चुअल नहीं है।
BigSandwich

40

जब भी आपकी कक्षा बहुरूपी हो, विध्वंसक को आभासी बनाएं।


13

एक बेस क्लास के लिए एक पॉइंटर के माध्यम से विध्वंसक कॉलिंग

struct Base {
  virtual void f() {}
  virtual ~Base() {}
};

struct Derived : Base {
  void f() override {}
  ~Derived() override {}
};

Base* base = new Derived;
base->f(); // calls Derived::f
base->~Base(); // calls Derived::~Derived

वर्चुअल डिस्ट्रक्टर कॉल किसी अन्य वर्चुअल फ़ंक्शन कॉल से अलग नहीं है।

इसके लिए base->f(), कॉल को भेज दिया जाएगा Derived::f(), और यह उसी के लिए समान है base->~Base()- इसके ओवरराइडिंग फ़ंक्शन - को Derived::~Derived()कॉल किया जाएगा।

समान तब होता है जब विध्वंसक को अप्रत्यक्ष रूप से कहा जा रहा है, जैसे delete base;deleteबयान फोन करेगा base->~Base()जो करने के लिए भेजा जाएगा Derived::~Derived()

गैर-आभासी विध्वंसक के साथ सार वर्ग

यदि आप किसी सूचक के माध्यम से उसके आधार वर्ग में ऑब्जेक्ट को हटाने नहीं जा रहे हैं - तो वर्चुअल विध्वंसक होने की आवश्यकता नहीं है। बस इसे protectedऐसा बनाएं कि इसे गलती से नहीं कहा जाएगा:

// library.hpp

struct Base {
  virtual void f() = 0;

protected:
  ~Base() = default;
};

void CallsF(Base& base);
// CallsF is not going to own "base" (i.e. call "delete &base;").
// It will only call Base::f() so it doesn't need to access Base::~Base.

//-------------------
// application.cpp

struct Derived : Base {
  void f() override { ... }
};

int main() {
  Derived derived;
  CallsF(derived);
  // No need for virtual destructor here as well.
}

क्या ~Derived()सभी व्युत्पन्न वर्गों में स्पष्ट रूप से घोषित करना आवश्यक है , भले ही यह सिर्फ हो ~Derived() = default? या यह कि भाषा द्वारा निहित है (इसे सुरक्षित करने के लिए इसे छोड़ देना चाहिए)?
पोंकडूडल

@Wallacoloo नहीं, केवल तभी घोषित करें जब यह आवश्यक हो। उदाहरण के लिए इसे protectedअनुभाग में रखें, या यह सुनिश्चित करने के लिए कि यह प्रयोग करके आभासी है override
अब्येक

9

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


2
यह एक ही प्रश्न पर एक अलग दृष्टिकोण है। अगर हम बेस क्लास बनाम व्युत्पन्न वर्ग के बजाय इंटरफेस के संदर्भ में सोचते हैं तो यह स्वाभाविक निष्कर्ष है: यदि यह इंटरफ़ेस का हिस्सा है तो इसे आभासी बना दें। अगर यह नहीं है।
ड्रेगन ओस्टोजिक

2
इंटरफ़ेस की OO अवधारणा और C ++ शुद्ध आभासी वर्ग की समानता के लिए +1 । विध्वंसक के बारे में लागू होने की उम्मीद है : जो अक्सर अनावश्यक होता है। जब तक कोई वर्ग कच्चे डायनामिक रूप से आवंटित मेमोरी (जैसे, स्मार्ट पॉइंटर के माध्यम से नहीं), फ़ाइल हैंडल या डेटाबेस हैंडल जैसे संसाधन का प्रबंधन कर रहा है, तब तक संकलक द्वारा बनाई गई डिफ़ॉल्ट डिस्ट्रक्टर का उपयोग करके व्युत्पन्न कक्षाओं में ठीक है। और ध्यान दें कि यदि एक virtualबेस क्लास में एक विध्वंसक (या किसी भी फ़ंक्शन) को घोषित किया जाता है, तो यह स्वचालित रूप virtualसे एक व्युत्पन्न वर्ग में है, भले ही इसे घोषित नहीं किया गया हो।
डेविड आरआर

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

8

विध्वंसक के लिए वर्चुअल कीवर्ड आवश्यक है जब आप चाहते हैं कि विभिन्न विध्वंसक उचित क्रम का पालन करें, जबकि ऑब्जेक्ट बेस क्लास पॉइंटर के माध्यम से हटाए जा रहे हैं। उदाहरण के लिए:

Base *myObj = new Derived();
// Some code which is using myObj object
myObj->fun();
//Now delete the object
delete myObj ; 

यदि आपका बेस क्लास डिस्ट्रक्टर वर्चुअल है तो ऑब्जेक्ट्स ऑर्डर (पहले व्युत्पन्न ऑब्जेक्ट फिर बेस) में डिस्ट्रक्ट हो जाएंगे। यदि आपका बेस क्लास डिस्ट्रक्टर वर्चुअल नहीं है, तो केवल बेस क्लास ऑब्जेक्ट डिलीट हो जाएगा (क्योंकि पॉइंटर बेस क्लास "बेस * मायऑबज" का है)। तो व्युत्पन्न वस्तु के लिए स्मृति रिसाव होगा।


7

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

 #include<iostream>
 using namespace std;
 class B{
    public:
       B(){
          cout<<"B()\n";
       }
       virtual ~B(){ 
          cout<<"~B()\n";
       }
 };
 class D: public B{
    public:
       D(){
          cout<<"D()\n";
       }
       ~D(){
          cout<<"~D()\n";
       }
 };
 int main(){
    B *b = new D();
    delete b;
    return 0;
 }

OUTPUT:
B()
D()
~D()
~B()

==============
If you don't give ~B()  as virtual. then output would be 
B()
D()
~B()
where destruction of ~D() is not done which leads to leak


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

@JamesAdkison क्यों अपरिभाषित व्यवहार का नेतृत्व करता है ??
रिमलोनफायर

@rimiro यह वही है जो मानक कहता है । मेरे पास एक प्रति नहीं है, लेकिन लिंक आपको एक टिप्पणी पर ले जाता है जहां कोई मानक के भीतर स्थान का संदर्भ देता है।
जेम्स एडिसन

@rimiro "यदि विलोपन, इसलिए, बेस क्लास इंटरफ़ेस के माध्यम से पॉलीमॉर्फिक रूप से प्रदर्शन किया जा सकता है, तो उसे वस्तुतः व्यवहार करना चाहिए और आभासी होना चाहिए। वास्तव में, भाषा को इसकी आवश्यकता होती है - यदि आप पॉलीमॉर्फिक को वर्चुअल डिस्ट्रक्टर के बिना हटाते हैं, तो आप खतरनाक दर्शक को बुलाते हैं। "अपरिभाषित व्यवहार," एक दर्शक जो मैं व्यक्तिगत रूप से एक मामूली रूप से अच्छी तरह से जली हुई गली में भी नहीं मिलूंगा, आपको बहुत-बहुत धन्यवाद। " ( gotw.ca/publications/mill18.htm ) - हर्ब सटर
जेम्स एडिसन

4

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


4

यदि आप उपयोग करते हैं shared_ptr(केवल shared_ptr, unique_ptr नहीं), तो आपके पास आधार वर्ग विध्वंसक नहीं होना चाहिए:

#include <iostream>
#include <memory>

using namespace std;

class Base
{
public:
    Base(){
        cout << "Base Constructor Called\n";
    }
    ~Base(){ // not virtual
        cout << "Base Destructor called\n";
    }
};

class Derived: public Base
{
public:
    Derived(){
        cout << "Derived constructor called\n";
    }
    ~Derived(){
        cout << "Derived destructor called\n";
    }
};

int main()
{
    shared_ptr<Base> b(new Derived());
}

उत्पादन:

Base Constructor Called
Derived constructor called
Derived destructor called
Base Destructor called

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

3

आभासी विध्वंसक क्या है या आभासी विध्वंसक का उपयोग कैसे किया जाता है

एक कक्षा विध्वंसक ~ ~ के साथ पूर्ववर्ती कक्षा के उसी नाम वाला एक फ़ंक्शन है जो कक्षा द्वारा आवंटित की गई मेमोरी को पुनः प्राप्त करेगा। हमें एक आभासी विध्वंसक की आवश्यकता क्यों है

कुछ आभासी कार्यों के साथ निम्नलिखित नमूना देखें

नमूना यह भी बताता है कि आप किसी पत्र को ऊपरी या निचले में कैसे बदल सकते हैं

#include "stdafx.h"
#include<iostream>
using namespace std;
// program to convert the lower to upper orlower
class convertch
{
public:
  //void convertch(){};
  virtual char* convertChar() = 0;
  ~convertch(){};
};

class MakeLower :public convertch
{
public:
  MakeLower(char *passLetter)
  {
    tolower = true;
    Letter = new char[30];
    strcpy(Letter, passLetter);
  }

  virtual ~MakeLower()
  {
    cout<< "called ~MakeLower()"<<"\n";
    delete[] Letter;
  }

  char* convertChar()
  {
    size_t len = strlen(Letter);
    for(int i= 0;i<len;i++)
      Letter[i] = Letter[i] + 32;
    return Letter;
  }

private:
  char *Letter;
  bool tolower;
};

class MakeUpper : public convertch
{
public:
  MakeUpper(char *passLetter)
  {
    Letter = new char[30];
    toupper = true;
    strcpy(Letter, passLetter);
  }

  char* convertChar()
  {   
    size_t len = strlen(Letter);
    for(int i= 0;i<len;i++)
      Letter[i] = Letter[i] - 32;
    return Letter;
  }

  virtual ~MakeUpper()
  {
    cout<< "called ~MakeUpper()"<<"\n";
    delete Letter;
  }

private:
  char *Letter;
  bool toupper;
};


int _tmain(int argc, _TCHAR* argv[])
{
  convertch *makeupper = new MakeUpper("hai"); 
  cout<< "Eneterd : hai = " <<makeupper->convertChar()<<" ";     
  delete makeupper;
  convertch *makelower = new MakeLower("HAI");;
  cout<<"Eneterd : HAI = " <<makelower->convertChar()<<" "; 
  delete makelower;
  return 0;
}

उपरोक्त नमूने से आप देख सकते हैं कि MakeUpper और MakeLower वर्ग दोनों के लिए विध्वंसक को नहीं कहा जाता है।

वर्चुअल विध्वंसक के साथ अगला नमूना देखें

#include "stdafx.h"
#include<iostream>

using namespace std;
// program to convert the lower to upper orlower
class convertch
{
public:
//void convertch(){};
virtual char* convertChar() = 0;
virtual ~convertch(){}; // defined the virtual destructor

};
class MakeLower :public convertch
{
public:
MakeLower(char *passLetter)
{
tolower = true;
Letter = new char[30];
strcpy(Letter, passLetter);
}
virtual ~MakeLower()
{
cout<< "called ~MakeLower()"<<"\n";
      delete[] Letter;
}
char* convertChar()
{
size_t len = strlen(Letter);
for(int i= 0;i<len;i++)
{
Letter[i] = Letter[i] + 32;

}

return Letter;
}

private:
char *Letter;
bool tolower;

};
class MakeUpper : public convertch
{
public:
MakeUpper(char *passLetter)
{
Letter = new char[30];
toupper = true;
strcpy(Letter, passLetter);
}
char* convertChar()
{

size_t len = strlen(Letter);
for(int i= 0;i<len;i++)
{
Letter[i] = Letter[i] - 32;
}
return Letter;
}
virtual ~MakeUpper()
{
      cout<< "called ~MakeUpper()"<<"\n";
delete Letter;
}
private:
char *Letter;
bool toupper;
};


int _tmain(int argc, _TCHAR* argv[])
{

convertch *makeupper = new MakeUpper("hai");

cout<< "Eneterd : hai = " <<makeupper->convertChar()<<" \n";

delete makeupper;
convertch *makelower = new MakeLower("HAI");;
cout<<"Eneterd : HAI = " <<makelower->convertChar()<<"\n ";


delete makelower;
return 0;
}

वर्चुअल डिस्ट्रॉक्टर स्पष्ट रूप से क्लास के सबसे व्युत्पन्न रन टाइम डिस्ट्रक्टर को बुलाएगा ताकि वह ऑब्जेक्ट को उचित तरीके से साफ़ कर सके।

या लिंक पर जाएँ

https://web.archive.org/web/20130822173509/http://www.programminggallery.com/article_details.php?article_id=138


2

जब आपको बेस क्लास से व्युत्पन्न वर्ग विध्वंसक को कॉल करने की आवश्यकता होती है। आपको बेस क्लास में वर्चुअल बेस क्लास डिस्ट्रक्टर घोषित करने की आवश्यकता है।


2

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

class A
{
public:
    A() {}
    virtual void foo()
    {
        cout << "This is A." << endl;
    }
};

class B : public A
{
public:
    B() {}
    void foo()
    {
        cout << "This is B." << endl;
    }
};

int main(int argc, char* argv[])
{
    A *a = new B();
    a->foo();
    if(a != NULL)
    delete a;
    return 0;
}

प्रिंट आउट निकालेंगे:

This is B.

इसके बिना virtualप्रिंट आउट होगा:

This is A.

और अब आपको समझना चाहिए कि वर्चुअल डिस्ट्रक्टर्स का उपयोग कब करना है।


नहीं, यह केवल वर्चुअल फ़ंक्शंस की पूरी तरह से मूल बातें को रोकता है, जब / क्यों विनाशकारी एक होना चाहिए - की सहजता की अनदेखी पूरी तरह से है - जो उतना सहज नहीं है, इसलिए ओपी ने सवाल क्यों पूछा। (इसके अलावा, क्यों अनावश्यक गतिशील आवंटन यहाँ बस करना B b{}; A& a{b}; a.foo();के लिए जांच की जा रही। NULL- जो होना चाहिए nullptr- इससे पहले कि deleteing - गलत indendation साथ - की आवश्यकता नहीं है: delete nullptr;। नो-सेशन के रूप में परिभाषित किया गया है कुछ भी है, तो आप कॉल करने से पहले यह चेक किया जाना चाहिए था, तो ->foo(), अन्यथा अन्यथा अपरिभाषित व्यवहार हो सकता है अगर newकिसी तरह विफल रहा हो।)
अंडरस्कोर_ड

2
deleteएक NULLपॉइंटर पर कॉल करना सुरक्षित है (यानी, आपको if (a != NULL)गार्ड की आवश्यकता नहीं है )।
जेम्स एडिसन

@SaileshD हाँ, मुझे पता है। यही मैंने अपनी टिप्पणी
जेम्स एडिसन

1

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

#include <iostream>
using namespace std;

struct a
{
    ~a() {}

    unsigned long long i;
};

struct b : a
{
    ~b() {}

    unsigned long long j;
};

struct c : b
{
    ~c() {}

    virtual void m3() {}

    unsigned long long k;
};

struct d : c
{
    ~d() {}

    virtual void m4() {}

    unsigned long long l;
};

int main()
{
    cout << "sizeof(a): " << sizeof(a) << endl;
    cout << "sizeof(b): " << sizeof(b) << endl;
    cout << "sizeof(c): " << sizeof(c) << endl;
    cout << "sizeof(d): " << sizeof(d) << endl;

    // No issue.

    a* a1 = new a();
    cout << "a1: " << a1 << endl;
    delete a1;

    // No issue.

    b* b1 = new b();
    cout << "b1: " << b1 << endl;
    cout << "(a*) b1: " << (a*) b1 << endl;
    delete b1;

    // No issue.

    c* c1 = new c();
    cout << "c1: " << c1 << endl;
    cout << "(b*) c1: " << (b*) c1 << endl;
    cout << "(a*) c1: " << (a*) c1 << endl;
    delete c1;

    // No issue.

    d* d1 = new d();
    cout << "d1: " << d1 << endl;
    cout << "(c*) d1: " << (c*) d1 << endl;
    cout << "(b*) d1: " << (b*) d1 << endl;
    cout << "(a*) d1: " << (a*) d1 << endl;
    delete d1;

    // Doesn't crash, but may not produce the results you want.

    c1 = (c*) new d();
    delete c1;

    // Crashes due to passing an invalid address to the method which
    // frees the memory.

    d1 = new d();
    b1 = (b*) d1;
    cout << "d1: " << d1 << endl;
    cout << "b1: " << b1 << endl;
    delete b1;  

/*

    // This is similar to what's happening above in the "crash" case.

    char* buf = new char[32];
    cout << "buf: " << (void*) buf << endl;
    buf += 8;
    cout << "buf after adding 8: " << (void*) buf << endl;
    delete buf;
*/
}

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

यदि आप उपरोक्त कोड चलाते हैं, तो समस्या होने पर आप स्पष्ट रूप से देखेंगे। जब बेस क्लास (/ स्ट्रक्चर) का यह पॉइंटर व्युत्पन्न वर्ग (/ स्ट्रक्चर) के इस पॉइंटर से अलग होता है, तो आप इस समस्या में भाग लेने वाले हैं। ऊपर के नमूने में, संरचना a और b में vtables नहीं है। संरचनाएं c और d में vtables हैं। इस प्रकार एक या बी पॉइंटर को एसी या डी ऑब्जेक्ट उदाहरण पर वाइबेट के लिए खाते में तय किया जाएगा। यदि आप इसे हटाने के लिए एक या b पॉइंटर पास करते हैं तो यह पता चलेगा कि पता हीप के नियमित रूटीन के लिए अमान्य है।

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


0

के बारे में एक मूल परिभाषा virtual में यह निर्धारित करती है कि क्या किसी वर्ग का सदस्य कार्य अपनी व्युत्पन्न कक्षाओं में अतिउपयोग कर सकता है।

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

जैसे ही निर्देश निष्पादित किया जाता है, बेस क्लास डिस्ट्रक्टर को बुलाया जाता है, लेकिन व्युत्पन्न के लिए नहीं।

एक प्रतीकात्मक उदाहरण है, जब नियंत्रण क्षेत्र में, आपको प्रभावकों, एक्ट्यूएटर्स में हेरफेर करना होगा।

कार्यक्षेत्र के अंत में, यदि पावर तत्वों (एक्ट्यूएटर) में से किसी एक के विनाशकर्ता को नहीं बुलाया जाता है, तो इसके घातक परिणाम होंगे।

#include <iostream>

class Mother{

public:

    Mother(){

          std::cout<<"Mother Ctor"<<std::endl;
    }

    virtual~Mother(){

        std::cout<<"Mother D-tor"<<std::endl;
    }


};

class Child: public Mother{

    public:

    Child(){

        std::cout<<"Child C-tor"<<std::endl;
    }

    ~Child(){

         std::cout<<"Child D-tor"<<std::endl;
    }
};

int main()
{

    Mother *c = new Child();
    delete c;

    return 0;
}

-1

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

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


मैं कहूंगा कि यह केवल आवश्यक है "अगर यह एक बेस क्लास पॉइंटर द्वारा इंगित किया जा सकता है " और पारंपरिक रूप से हटाया जा सकता है। लेकिन मुझे लगता है कि बाद में जरूरत पड़ने पर वर्चुअल डार्टर्स को जोड़ने की आदत में शामिल होने के लिए यह चोट नहीं पहुंचाता।
अंडरस्कोर_ड
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.