हमें C ++ में शुद्ध आभासी विध्वंसक की आवश्यकता क्यों है?


154

मैं एक आभासी विध्वंसक की आवश्यकता को समझता हूं। लेकिन हमें शुद्ध आभासी विध्वंसक की आवश्यकता क्यों है? C ++ लेखों में से एक में, लेखक ने उल्लेख किया है कि हम शुद्ध आभासी विध्वंसक का उपयोग करते हैं जब हम एक वर्ग सार बनाना चाहते हैं।

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

तो मेरे सवाल हैं

  1. हम वास्तव में एक विध्वंसक शुद्ध आभासी कब बनाते हैं? क्या कोई अच्छा वास्तविक समय उदाहरण दे सकता है?

  2. जब हम अमूर्त कक्षाएं बना रहे हैं तो क्या विध्वंसक को भी शुद्ध आभासी बनाना अच्छा है? यदि हाँ..तो क्यों?


4
एकाधिक डुप्लिकेट: stackoverflow.com/questions/999340/… और stackoverflow.com/questions/630950/pure-virtual-destructor-in-c उनमें से दो जा रहा है
डैनियल स्लॉफ़

14
@ डैनियल- उल्लेखित लिंक मेरे सवाल का जवाब नहीं देता है। यह उत्तर देता है कि शुद्ध आभासी विध्वंसक की परिभाषा क्यों होनी चाहिए। मेरा प्रश्न यह है कि हमें एक शुद्ध आभासी विध्वंसक की आवश्यकता क्यों है।
मार्क

मैं इसका कारण जानने की कोशिश कर रहा था, लेकिन आपने पहले ही यहाँ सवाल पूछ लिया था।
nsivakr

जवाबों:


119
  1. संभवतः असली कारण यह है कि शुद्ध आभासी विध्वंसक को अनुमति दी जाती है कि उन्हें निषिद्ध करने का मतलब भाषा में एक और नियम जोड़ना होगा और इस नियम की कोई आवश्यकता नहीं है क्योंकि कोई भी प्रभाव शुद्ध आभासी विनाशकारी की अनुमति नहीं दे सकता है।

  2. नहीं, सादे पुराने आभासी पर्याप्त है।

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

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

एक यह भी मान सकता है कि प्रत्येक व्युत्पन्न वर्ग को संभवतः विशिष्ट क्लीन-अप कोड की आवश्यकता होगी और एक को लिखने के लिए एक अनुस्मारक के रूप में शुद्ध आभासी विध्वंसक का उपयोग करना होगा लेकिन ऐसा लगता है कि यह आकस्मिक (और अनपेक्षित) है।

नोट: नाशक एकमात्र तरीका भले ही वह यह है कि है शुद्ध आभासी है इन्स्तांत प्राप्त होने वाले वर्गों (हाँ शुद्ध आभासी कार्यों कार्यान्वयन हो सकता है) के क्रम में एक कार्यान्वयन है।

struct foo {
    virtual void bar() = 0;
};

void foo::bar() { /* default implementation */ }

class foof : public foo {
    void bar() { foo::bar(); } // have to explicitly call default implementation.
};

13
"हाँ शुद्ध आभासी कार्यों में कार्यान्वयन हो सकते हैं" फिर यह शुद्ध आभासी नहीं है।
GManNickG

2
यदि आप एक वर्ग को अमूर्त बनाना चाहते हैं, तो क्या यह आसान नहीं होगा कि सभी निर्माणकर्ताओं को संरक्षित किया जाए?
bdonlan

78
@GMan, आप गलत हैं, शुद्ध आभासी होने के नाते व्युत्पन्न वर्गों को इस पद्धति को ओवरराइड करना चाहिए, यह एक कार्यान्वयन होने के लिए रूढ़िवादी है। foof::barयदि आप अपने लिए देखना चाहते हैं तो मेरा कोड देखें और टिप्पणी करें।
19

15
@ मेन: सी ++ एफएक्यू लाइट का कहना है कि "ध्यान दें कि शुद्ध आभासी फ़ंक्शन के लिए एक परिभाषा प्रदान करना संभव है, लेकिन यह आमतौर पर नौसिखियों को भ्रमित करता है और बाद तक सबसे अच्छा बचा जाता है।" parashift.com/c++-faq-lite/abcs.html#faq-22.4 विकिपीडिया (शुद्धता का गढ़) भी इसी तरह कहता है। मेरा मानना ​​है कि ISO / IEC मानक समान शब्दावली का उपयोग करता है (दुर्भाग्यवश मेरी कॉपी इस समय काम पर है) ... मैं मानता हूं कि यह भ्रामक है, और मैं आमतौर पर स्पष्टीकरण के बिना शब्द का उपयोग नहीं करता जब मैं एक परिभाषा प्रदान कर रहा हूं, विशेष रूप से लगभग नए प्रोग्रामर ...
leander

9
@Motti: यहां क्या दिलचस्प है और अधिक भ्रम प्रदान करता है कि शुद्ध आभासी विध्वंसक को व्युत्पन्न (और त्वरित) वर्ग में स्पष्ट रूप से ओवरराइड करने की आवश्यकता नहीं है। ऐसी स्थिति में निहित परिभाषा का उपयोग किया जाता है :)
kappa

33

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


1
लेकिन फिर भी क्यों शुद्ध पुण्य विनाशक के कार्यान्वयन प्रदान करने के लिए। संभवतः यह गलत हो सकता है कि मैं एक विध्वंसक शुद्ध आभासी बनाता हूं और इसके कार्यान्वयन को प्रदान नहीं करता है। मेरा मानना ​​है कि केवल बेस क्लास पॉइंटर्स घोषित किए जाते हैं और इसलिए एब्सट्रैक्ट क्लास के लिए विनाशकारी कभी नहीं कहा जाता है।
कृष्णा ओझा

4
@ सर्फिंग: क्योंकि एक व्युत्पन्न वर्ग का एक विध्वंसक अंतर्निहित रूप से अपने बेस क्लास के विनाशकर्ता को बुलाता है, भले ही वह विध्वंसक शुद्ध आभासी हो। इसलिए यदि इसके लिए कोई क्रियान्वयन नहीं होता है तो अपरिभाषित व्यवहार होने वाला है।
8

19

यदि आप एक सार आधार वर्ग बनाना चाहते हैं:

  • कि instantiated नहीं किया जा सकता (हां, यह शब्द "सार" के साथ बेमानी है!)
  • लेकिन आभासी विध्वंसक व्यवहार की आवश्यकता है (आप व्युत्पन्न प्रकारों की ओर संकेत करते हुए एबीसी के चारों ओर ले जाने का इरादा रखते हैं, और इसके माध्यम से)
  • लेकिन अन्य तरीकों के लिए किसी अन्य आभासी प्रेषण व्यवहार की आवश्यकता नहीं है (हो सकता है कि कोई अन्य विधियां न हों; एक साधारण संरक्षित "संसाधन" कंटेनर पर विचार करें, जिसके लिए एक कंस्ट्रक्टर / विध्वंसक / असाइनमेंट की आवश्यकता है लेकिन बहुत कुछ नहीं)

... विध्वंसक को शुद्ध आभासी बनाकर और उसके लिए एक परिभाषा (विधि निकाय) प्रदान करके कक्षा को अमूर्त बनाना सबसे आसान है।

हमारे काल्पनिक एबीसी के लिए:

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


8

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

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

मेरी राय में, शुद्ध आभासी विध्वंसक उपयोगी हो सकते हैं। उदाहरण के लिए, मान लें कि आपके पास अपने कोड में दो क्लासेस myClassA और myClassB हैं, और यह कि myClassB को myClassA से विरासत में मिला है। स्कॉट मेयर्स ने अपनी पुस्तक "मोर इफेक्टिव सी ++", आइटम 33 "मेकिंग नॉन-लीफ क्लास एब्सट्रैक्ट" में उल्लिखित कारणों के लिए, वास्तव में एक अमूर्त वर्ग मायएब्रेटक्लास बनाना जिसमें से myClassA और myClassB इनहेरिट करना बेहतर है। यह बेहतर अमूर्तता प्रदान करता है और उदाहरण के लिए, वस्तु प्रतियों के साथ आने वाली कुछ समस्याओं को रोकता है।

अमूर्त प्रक्रिया (वर्ग myAbstractClass बनाने की) में, यह हो सकता है कि myClassA या myClassB की कोई विधि शुद्ध आभासी विधि होने के लिए एक अच्छा उम्मीदवार नहीं है (जो कि myAbstractClass के लिए एक पूर्वापेक्षा है)। इस स्थिति में, आप अमूर्त वर्ग के विध्वंसक शुद्ध आभासी को परिभाषित करते हैं।

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

struct IParams
{
    IParams(const ModelConfiguration& aModelConf);
    virtual ~IParams() = 0;

    void setParameter(const N_Configuration::Parameter& aParam);

    std::map<std::string, std::string> m_Parameters;
};

struct NumericsParams : IParams
{
    NumericsParams(const ModelConfiguration& aNumericsConf);
    virtual ~NumericsParams();

    double dt() const;
    double ti() const;
    double tf() const;
};

struct PhysicsParams : IParams
{
    PhysicsParams(const N_Configuration::ModelConfiguration& aPhysicsConf);
    virtual ~PhysicsParams();

    double g()     const; 
    double rho_i() const; 
    double rho_w() const; 
};

1
मुझे यह उपयोग पसंद है, लेकिन विरासत को "लागू करने" का एक और तरीका यह है कि निर्माणकर्ता को IParamसंरक्षित घोषित किया जाए, जैसा कि कुछ अन्य टिप्पणी में उल्लेख किया गया था।
15'15

4

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


3

यहां मैं यह बताना चाहता हूं कि कब हमें वर्चुअल डिस्ट्रक्टर की जरूरत है और कब हमें शुद्ध वर्चुअल डिस्ट्रक्टर की जरूरत है

class Base
{
public:
    Base();
    virtual ~Base() = 0; // Pure virtual, now no one can create the Base Object directly 
};

Base::Base() { cout << "Base Constructor" << endl; }
Base::~Base() { cout << "Base Destructor" << endl; }


class Derived : public Base
{
public:
    Derived();
    ~Derived();
};

Derived::Derived() { cout << "Derived Constructor" << endl; }
Derived::~Derived() {   cout << "Derived Destructor" << endl; }


int _tmain(int argc, _TCHAR* argv[])
{
    Base* pBase = new Derived();
    delete pBase;

    Base* pBase2 = new Base(); // Error 1   error C2259: 'Base' : cannot instantiate abstract class
}
  1. जब आप चाहते हैं कि कोई भी सीधे आधार वर्ग की वस्तु बनाने में सक्षम न हो, तो शुद्ध आभासी विध्वंसक का उपयोग करें virtual ~Base() = 0। आमतौर पर कम से कम एक शुद्ध आभासी फ़ंक्शन की आवश्यकता होती है, चलो virtual ~Base() = 0इस फ़ंक्शन के रूप में लेते हैं ।

  2. जब आपको उपरोक्त चीज़ की आवश्यकता नहीं होती है, केवल आपको व्युत्पन्न वर्ग वस्तु के सुरक्षित विनाश की आवश्यकता होती है

    आधार * pBase = नया व्युत्पन्न (); हटाना pBase; शुद्ध आभासी विध्वंसक की आवश्यकता नहीं है, केवल आभासी विध्वंसक ही काम करेगा।


2

आप इन उत्तरों के साथ काल्पनिक रूप से प्राप्त कर रहे हैं, इसलिए मैं स्पष्टता के लिए पृथ्वी के स्पष्टीकरण के लिए और अधिक सरल बनाने की कोशिश करूंगा।

वस्तु उन्मुख डिजाइन के मूल संबंध दो हैं: आईएस-ए और एचएएस-ए। मैंने उन लोगों को नहीं बनाया। यही उन्हें कहा जाता है।

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

हैस ने संकेत दिया है कि एक वस्तु एक समग्र वर्ग का हिस्सा है और एक स्वामित्व संबंध है। C ++ में इसका मतलब यह है कि यह एक सदस्य वस्तु है और जैसे कि onus खुद को नष्ट करने से पहले इसे निपटाने या स्वामित्व को बंद करने के लिए मालिक वर्ग पर है।

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

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

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

फ्रूट क्लास में वर्चुअल फ़ंक्शन रंग () हो सकता है जो डिफ़ॉल्ट रूप से "NONE" लौटाता है। केले वर्ग का रंग () फ़ंक्शन "YELLOW" या "BROWN" देता है।

लेकिन अगर फलों के पॉइंटर को लेने वाले फंक्शन में भेजे गए केले के वर्ग पर रंग () कॉल किया जाता है - कौन सा रंग () फंक्शन इनवैलिड हो जाता है? फल फल के लिए फंक्शन आमतौर पर फ्रूट :: कलर () कहलाता है।

99% उस समय नहीं होगा जो इरादा था। लेकिन अगर Fruit :: color () को वर्चुअल घोषित किया गया था तो Banana: color () को ऑब्जेक्ट के लिए कहा जाएगा क्योंकि कॉल के समय सही रंग () फ़ंक्शन फ्रूट पॉइंटर के लिए बाध्य होगा। रनटाइम यह जांच करेगा कि पॉइंटर पॉइंट किस ऑब्जेक्ट के कारण है क्योंकि यह फ्रूट क्लास की परिभाषा में वर्चुअल चिह्नित किया गया था।

यह एक उपवर्ग में एक फ़ंक्शन को ओवरराइड करने से अलग है। उस स्थिति में फ्रूट पॉइंटर फ्रूट :: कलर () कहेगा, अगर सभी को पता है कि यह फ्रूट का आईएस-ए पॉइंटर है।

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

अब पहेली का अंतिम टुकड़ा: निर्माता और विध्वंसक।

शुद्ध वर्चुअल कंस्ट्रक्टर अवैध हैं, पूरी तरह से। वह अभी बाहर है।

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

 virtual ~Fruit() = 0;  // pure virtual 
 Fruit::~Fruit(){}      // destructor implementation

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

इसलिए आपको इस मामले में फल के उदाहरण बनाने से मना किया जाता है, लेकिन केले के उदाहरण बनाने की अनुमति है।

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

क्या यह एक बुरा मॉडल है? यह डिजाइन चरण में अधिक जटिल है, हां, लेकिन यह सुनिश्चित कर सकता है कि रन-टाइम पर सही लिंकिंग की जाती है और एक उपवर्ग कार्य किया जाता है जहां अस्पष्टता होती है कि वास्तव में किस उपवर्ग तक पहुँचा जा रहा है।

यदि आप C ++ लिखते हैं, ताकि आप केवल सटीक क्लास पॉइंटर्स के साथ पास करें, जिसमें कोई सामान्य और अस्पष्ट बिंदु नहीं हैं, तो वर्चुअल फ़ंक्शन की वास्तव में आवश्यकता नहीं है। लेकिन अगर आपको प्रकार के रन-टाइम लचीलेपन की आवश्यकता होती है (जैसा कि Apple Banana Orange ==> Fruit) फ़ंक्शन कम निरर्थक कोड के साथ आसान और अधिक बहुमुखी हो जाते हैं। अब आपको प्रत्येक प्रकार के फलों के लिए एक फ़ंक्शन लिखना होगा, और आप जानते हैं कि प्रत्येक फल अपने सही फ़ंक्शन के साथ रंग () में जवाब देगा।

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


1

यह एक दशक पुराना विषय है :) विवरण के लिए "प्रभावी C ++" पुस्तक पर आइटम # 7 के अंतिम 5 पैराग्राफ को पढ़ें, "कभी-कभार शुरू होता है" एक कक्षा को एक शुद्ध आभासी विनाशकारी देने के लिए सुविधाजनक हो सकता है .... "


0

आपने एक उदाहरण के लिए पूछा, और मेरा मानना ​​है कि निम्नलिखित एक शुद्ध आभासी विध्वंसक के लिए एक कारण प्रदान करता है। मैं उत्तर के लिए तत्पर हूं कि क्या यह अच्छा है कारण है ...

मैं नहीं चाहता कि कोई भी व्यक्ति error_baseप्रकार को फेंकने में सक्षम हो , लेकिन अपवाद प्रकार error_oh_shucksऔर error_oh_blastसमान कार्यक्षमता है और मैं इसे दो बार लिखना नहीं चाहता। PImpl जटिलता std::stringमेरे ग्राहकों, और के उपयोग को उजागर करने से बचने के लिए आवश्यक हैstd::auto_ptr प्रतिलिपि निर्माता आवश्यकता है।

सार्वजनिक शीर्ष लेख में अपवाद विनिर्देश हैं जो क्लाइंट को मेरे पुस्तकालय द्वारा फेंके जा रहे विभिन्न प्रकार के अपवादों को अलग करने के लिए उपलब्ध होंगे:

// error.h

#include <exception>
#include <memory>

class exception_string;

class error_base : public std::exception {
 public:
  error_base(const char* error_message);
  error_base(const error_base& other);
  virtual ~error_base() = 0; // Not directly usable

  virtual const char* what() const;
 private:
  std::auto_ptr<exception_string> error_message_;
};

template<class error_type>
class error : public error_base {
 public:
   error(const char* error_message) : error_base(error_message) {}
   error(const error& other) : error_base(other) {}
   ~error() {}
};

// Neither should these classes be usable
class error_oh_shucks { virtual ~error_oh_shucks() = 0; }
class error_oh_blast { virtual ~error_oh_blast() = 0; }

और यहाँ साझा कार्यान्वयन है:

// error.cpp

#include "error.h"
#include "exception_string.h"

error_base::error_base(const char* error_message)
  : error_message_(new exception_string(error_message)) {}

error_base::error_base(const error_base& other)
  : error_message_(new exception_string(other.error_message_->get())) {}

error_base::~error_base() {}

const char* error_base::what() const {
  return error_message_->get();
}

अपवाद_string वर्ग, निजी रखा जाता है, std :: string को मेरे सार्वजनिक इंटरफ़ेस से:

// exception_string.h

#include <string>

class exception_string {
 public:
  exception_string(const char* message) : message_(message) {}

  const char* get() const { return message_.c_str(); }
 private:
  std::string message_;
};

मेरा कोड तब एक त्रुटि के रूप में फेंकता है:

#include "error.h"

throw error<error_oh_shucks>("That didn't work");

के लिए एक टेम्पलेट का उपयोग errorथोड़ा आभारी है। यह ग्राहकों को त्रुटियों को पकड़ने के लिए आवश्यक कोड की थोड़ी सी बचत करता है:

// client.cpp

#include <error.h>

try {
} catch (const error<error_oh_shucks>&) {
} catch (const error<error_oh_blast>&) {
}

0

हो सकता है कि शुद्ध आभासी विध्वंसक का एक और वास्तविक उपयोग है, जिसे मैं वास्तव में अन्य उत्तरों में नहीं देख सकता हूं :)

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

पहले इसकी कल्पना करें:

class Printable {
  virtual void print() const = 0;
  // virtual destructor should be here, but not to confuse with another problem
};

और कुछ इस तरह:

class Printer {
  void queDocument(unique_ptr<Printable> doc);
  void printAll();
};

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

और अब बिलकुल उसी तरह की कल्पना कीजिए, जो मुद्रण के लिए नहीं बल्कि विनाश के लिए है:

class Destroyable {
  virtual ~Destroyable() = 0;
};

और यह भी एक समान कंटेनर हो सकता है:

class PostponedDestructor {
  // Queues an object to be destroyed later.
  void queObjectForDestruction(unique_ptr<Destroyable> obj);
  // Destroys all already queued objects.
  void destroyAll();
};

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

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


-2

1) जब आप सफाई करने के लिए व्युत्पन्न वर्गों की आवश्यकता चाहते हैं। यह दुर्लभ है।

2) नहीं, लेकिन आप चाहते हैं कि यह आभासी हो, हालांकि।


-2

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


-1: सवाल यह नहीं है कि विध्वंसक आभासी क्यों होना चाहिए।
Troubadour

इसके अलावा, कुछ स्थितियों में विनाशकारी को सही विनाश प्राप्त करने के लिए आभासी होना आवश्यक नहीं है। वर्चुअल डिस्ट्रक्टर्स की आवश्यकता तब होती है जब आप deleteएक पॉइंटर को बेस क्लास पर बुलाते हैं जब यह वास्तव में इसकी व्युत्पत्ति की ओर इशारा करता है।
CygnusX1

आप 100% सही हैं। यह सी ++ कार्यक्रमों में लीक और क्रैश के नंबर एक स्रोतों में से पिछले एक में रहा है, केवल शून्य बिंदुओं के साथ काम करने और सरणियों की सीमा से अधिक करने की कोशिश करने के लिए तीसरा है। एक गैर-वर्चुअल बेस क्लास डिस्ट्रक्टर को जेनेरिक पॉइंटर पर बुलाया जाएगा, उपवर्ग डिस्ट्रक्टर को पूरी तरह से दरकिनार कर दिया जाए अगर यह वर्चुअल नहीं है। यदि उप-वर्ग से संबंधित कोई गतिशील रूप से निर्मित वस्तुएं हैं, तो उन्हें हटाने के लिए एक कॉल पर आधार विध्वंसक द्वारा पुनर्प्राप्त नहीं किया जाएगा। आप जुर्माने के साथ जुगाड़ कर रहे हैं तो BLUURRK! (यह पता लगाना कठिन है कि कहां है।)
क्रिस रीड
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.