कार्यान्वयन के साथ शुद्ध आभासी कार्य


176

मेरी बुनियादी समझ यह है कि शुद्ध आभासी फ़ंक्शन के लिए कोई कार्यान्वयन नहीं है, हालांकि, मुझे बताया गया था कि शुद्ध आभासी फ़ंक्शन के लिए कार्यान्वयन हो सकता है।

class A {
public:
    virtual void f() = 0;
};

void A::f() {
    cout<<"Test"<<endl;
}

क्या कोड ठीक है?

इसे लागू करने के साथ एक शुद्ध आभासी कार्य करने का क्या उद्देश्य है?

जवाबों:


215

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

class B : public A {

    virtual void f() {
        // class B doesn't have anything special to do for f()
        //  so we'll call A's

        // note that A's declaration of f() would have to be public 
        //  or protected to avoid a compile time problem

        A::f();
    }

};

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

ध्यान दें कि भले ही यह भाषा द्वारा अनुमति दी गई हो, यह ऐसी चीज नहीं है जिसे मैं आमतौर पर इस्तेमाल किया जाता हूं (और यह तथ्य कि यह किया जा सकता है सबसे सी ++ प्रोग्रामर, यहां तक ​​कि अनुभवी भी आश्चर्यचकित करते हैं)।


1
आप जोड़ना भूल गए कि यह प्रोग्रामर को आश्चर्यचकित क्यों करता है: यह इसलिए है क्योंकि इनलाइन परिभाषा मानक द्वारा निषिद्ध है। शुद्ध आभासी तरीके परिभाषाएँ होनी चाहिए deported। (या तो .inl या .cpp में सामान्य फ़ाइल-नामिंग प्रथाओं को देखें)।
v.oddou

इसलिए यह कॉलिंग विधि स्थिर विधि सदस्य कॉलिंग के समान है। जावा में वर्ग विधि के कुछ प्रकार।
सान ल्वी

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

5
यह इंगित करने के लायक है कि ए :: एफ () शुद्ध बनाने का मतलब है कि बी को एफ () लागू करना होगा (अन्यथा बी सार और अपरिवर्तनीय होगा)। और जैसा कि @MichaelBurr बताता है, A :: f () के लिए कार्यान्वयन प्रदान करने का अर्थ है कि B इसका उपयोग f () परिभाषित करने के लिए कर सकता है
fearless_fool

2
IIRC, स्कॉट मेयर ने अपनी क्लासिक पुस्तक "मोर इफेक्टिव C ++" में एक इस प्रश्न के उपयोग के मामले के बारे में एक उत्कृष्ट लेख दिया है
irsis

75

स्पष्ट होने के लिए, आप गलत समझ रहे हैं कि क्या = 0; एक आभासी समारोह का मतलब है के बाद।

= 0 का अर्थ है कि व्युत्पन्न वर्गों को एक कार्यान्वयन प्रदान करना चाहिए, ऐसा नहीं है कि आधार वर्ग एक कार्यान्वयन प्रदान नहीं कर सकता है।

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


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

@rmn, हां, आप कंस्ट्रक्टरों में वर्चुअल कॉल के बारे में सही हैं। मैंने जवाब अपडेट किया। उम्मीद है कि हर कोई ऐसा नहीं करना जानता है, हालांकि। :)
टेरी महाफ़ेई

3
वास्तव में, एक कंस्ट्रक्टर से आधार शुद्ध कॉल करने से कार्यान्वयन-परिभाषित व्यवहार होता है। VC ++ में, वह एक _purecall क्रैश की मात्रा है।
टोक शिलोन

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

1
कंस्ट्रक्टर और डिस्ट्रक्टर्स में, वर्चुअल फ़ंक्शन वर्चुअल नहीं होते हैं ।
जेसपर जुहल

20

इसका लाभ यह है कि यह व्युत्पन्न प्रकारों को अभी भी विधि को ओवरराइड करने के लिए बाध्य करता है, लेकिन एक डिफ़ॉल्ट या additive कार्यान्वयन भी प्रदान करता है।


1
यदि कोई डिफ़ॉल्ट कार्यान्वयन है तो मैं क्यों मजबूर करना चाहूंगा? यह सामान्य आभासी कार्यों की तरह लगता है। यदि यह सिर्फ एक सामान्य आभासी कार्य था, तो मैं या तो ओवरराइड कर सकता हूं और यदि मैं नहीं करता हूं, तो एक डिफ़ॉल्ट कार्यान्वयन प्रदान किया जाएगा (आधार का कार्यान्वयन)।
StackExchange123

19

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

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

इसलिए शुद्ध आभासी कार्यों का एक समझदार उपयोग शुद्ध आभासी विध्वंसक को "गैर-अंतिम" कीवर्ड के रूप में निर्दिष्ट कर रहा है।

निम्नलिखित कोड आश्चर्यजनक रूप से सही है:

class Base {
public:
  virtual ~Base() = 0;
};

Base::~Base() {}

class Derived : public Base {};

int main() { 
  // Base b; -- compile error
  Derived d; 
}

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

1
वह कोड गलत है। आपको भाषा की वाक्य रचना के कारण वर्ग परिभाषा के बाहर के dtor को परिभाषित करना होगा।

@Roger: धन्यवाद, जिसने वास्तव में मेरी मदद की - यह वह कोड है जिसका मैं उपयोग कर रहा हूं, यह MSVC के तहत ठीक संकलित करता है, लेकिन मुझे लगता है कि यह पोर्टेबल नहीं होगा।
कोर्नेल किलिविक्ज


4

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

स्कॉट मेयर्स ने प्रभावी C ++ (द्वितीय संस्करण) मद # 36 में इंटरफ़ेस की विरासत और कार्यान्वयन की विरासत के बीच इस पर चर्चा की। आइटम संस्करण नवीनतम संस्करण में परिवर्तित हो सकता है।


4

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

यदि आपके व्युत्पन्न वर्ग आपके बेस क्लास कार्यान्वयन को कॉल करना चाहते हैं, तो बेस क्लास में शुद्ध आभासी फ़ंक्शन बॉडी उपयोगी हैं।


2

'वर्चुअल शून्य foo () = 0;' वाक्यविन्यास का मतलब यह नहीं है कि आप वर्तमान कक्षा में फू () को लागू नहीं कर सकते, आप कर सकते हैं। इसका मतलब यह भी नहीं है कि आपको इसे व्युत्पन्न वर्गों में लागू करना चाहिए । इससे पहले कि आप मुझे थप्पड़ मारें, आइए डायमंड प्रॉब्लम देखें: (इम्प्लिट्ट कोड, माइंड यू)।

class A
{
public: 
    virtual void foo()=0;
    virtual void bar();
}

class B : public virtual A
{
public:
    void foo() { bar(); }
}

class C : public virtual A
{
public:
    void bar();
}

class D : public B, public C
{}

int main(int argc, const char* argv[])
{
    A* obj = new D();
    **obj->foo();**
    return 0;
}

अब, obj-> foo () मंगलाचरण के परिणामस्वरूप B :: foo () और फिर C :: bar () होगा।

आप देखें ... शुद्ध आभासी विधियों को व्युत्पन्न वर्गों में लागू नहीं किया जाना है (फू) का कक्षा सी में कोई कार्यान्वयन नहीं है - संकलक संकलन करेगा) C ++ में बहुत अधिक खामियां हैं।

आशा है कि मैं मदद कर सकता है :-)


5
इसे सभी व्युत्पन्न वर्गों में लागू करने की आवश्यकता नहीं है, लेकिन इसे उन सभी व्युत्पन्न वर्गों में लागू करना होगा, जिन्हें आप तुरंत भेजना चाहते हैं। आप Cअपने उदाहरण में किसी प्रकार की वस्तु को तुरंत नहीं भेज सकते । आप किसी प्रकार की वस्तु को तुरंत भेज सकते हैं Dक्योंकि इससे उसका कार्यान्वयन हो जाता fooहै B
यंगजॉन

0

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

class Foo
{
   virtual ~Foo() = 0;
   void bar1() {}
   void bar2(int x) {}
   // other methods
};

Foo::~Foo()
{
}

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

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