व्युत्पन्न वर्गों में कार्यों के लिए C ++ "आभासी" कीवर्ड। क्या ये ज़रूरी हैं?


221

नीचे दी गई संरचनात्मक परिभाषा के साथ ...

struct A {
    virtual void hello() = 0;
};

दृष्टिकोण # 1:

struct B : public A {
    virtual void hello() { ... }
};

दृष्टिकोण # 2:

struct B : public A {
    void hello() { ... }
};

क्या हेलो फंक्शन को ओवरराइड करने के लिए इन दोनों तरीकों में कोई अंतर है?


65
C ++ 11 में आप "शून्य शून्य () ओवरराइड {}" को स्पष्ट रूप से घोषित करने के लिए लिख सकते हैं कि आप एक आभासी पद्धति को ओवरराइड कर रहे हैं। यदि आधार वर्चुअल विधि मौजूद नहीं है, तो कंपाइलर विफल हो जाएगा, और इसमें वंशज वर्ग पर "वर्चुअल" रखने के समान ही पठनीयता है।
शैडोचैस्टर

दरअसल, gcc के C ++ 11 में, व्युत्पन्न वर्ग में शून्य हेलो () ओवरराइड {} लिखना ठीक है क्योंकि बेस क्लास ने निर्दिष्ट किया है कि विधि हैलो () आभासी है। दूसरे शब्दों में, वैसे भी gcc / g ++ के लिए, व्युत्पन्न वर्ग में आभासी शब्द का उपयोग आवश्यक / अनिवार्य नहीं है। (मैं आरपीआई ३ पर gcc संस्करण ४.१.२ का उपयोग कर रहा हूँ) लेकिन यह अच्छी तरह से व्युत्पन्न वर्ग की विधि में कीवर्ड वर्चुअल को शामिल करने के लिए अच्छा अभ्यास है।
विल

जवाबों:


183

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


25
यह सच है, लेकिन मोज़िला सी ++ पोर्टेबिलिटी गाइड हमेशा आभासी का उपयोग करने की सलाह देता है क्योंकि "कुछ संकलक" चेतावनी देते हैं यदि आप नहीं करते हैं। बहुत बुरे वे ऐसे संकलक के किसी भी उदाहरण का उल्लेख नहीं करते हैं।
सर्गेई टैचेनोव

5
मैं यह भी स्पष्ट रूप से जोड़ना चाहूंगा कि इसे आभासी के रूप में चिह्नित करने से आपको विध्वंसक को आभासी बनाने में मदद मिलेगी।
लेफलिन

1
केवल उल्लेख करने के लिए, आभासी विध्वंसक पर
अतुल

6
@SergeyTachenov अपने स्वयं के जवाब के लिए क्लिफर्ड की टिप्पणी के अनुसार , इस तरह के संकलक का उदाहरण आर्मक है।
रुस्लान

4
@ रस्मी, नया पोर्टेबिलिटी गाइड यहां है , लेकिन अब यह overrideकीवर्ड का उपयोग करने की सिफारिश करता है।
सर्गेई टैचेनोव

83

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

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


12
यह कौन सा संकलक है?
जेम्स मैकनेलिस

35
@ जेम्स: आर्मक (एआरएम उपकरणों के लिए एआरएम का कंपाइलर)
क्लिफर्ड

55

virtualकीवर्ड व्युत्पन्न वर्ग में आवश्यक नहीं है। यहाँ C ++ ड्रॉफ्ट स्टैंडर्ड (N3337) (जोर मेरा) से सहायक प्रलेखन है:

10.3 वर्चुअल फ़ंक्शंस

2 यदि एक आभासी सदस्य फ़ंक्शन vfको एक वर्ग Baseऔर एक वर्ग में घोषित किया जाता है Derived, जो प्रत्यक्ष या परोक्ष रूप से Baseएक vfही नाम, पैरामीटर-प्रकार-सूची (8.3.5), सीवी-योग्यता, और रेफ-क्वालिफायर ( या अनुपस्थिति) जैसा Base::vfकि घोषित किया गया है, तब Derived::vfभी आभासी है ( यह घोषित किया गया है या नहीं ) और यह ओवरराइड करता है Base::vf


5
यह अब तक का सबसे अच्छा जवाब है।
शानदार मिस्टर फॉक्स

33

नहीं, virtualव्युत्पन्न कक्षाओं के वर्चुअल फ़ंक्शन ओवरराइड पर कीवर्ड की आवश्यकता नहीं है। लेकिन यह संबंधित गड़बड़ी का उल्लेख करने योग्य है: एक आभासी फ़ंक्शन को ओवरराइड करने में विफलता।

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

हालांकि, यह औसतन C ++ 11 स्पष्ट ओवरराइड भाषा सुविधा द्वारा संबोधित किया जाता है , जो स्रोत कोड को स्पष्ट रूप से निर्दिष्ट करने की अनुमति देता है कि सदस्य फ़ंक्शन किसी बेस क्लास फ़ंक्शन को ओवरराइड करने का इरादा रखता है:

struct Base {
    virtual void some_func(float);
};

struct Derived : Base {
    virtual void some_func(int) override; // ill-formed - doesn't override a base class method
};

संकलक एक संकलन-समय त्रुटि जारी करेगा और प्रोग्रामिंग त्रुटि तुरंत स्पष्ट होगी (शायद व्युत्पन्न में फ़ंक्शन floatको तर्क के रूप में लिया जाना चाहिए )।

WP का संदर्भ लें : C ++ 11


11

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


7

संकलक के लिए कोई अंतर नहीं है, जब आप virtualव्युत्पन्न वर्ग में लिखते हैं या इसे छोड़ देते हैं।

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


2

जब आपके पास टेम्प्लेट होते हैं और बेस क्लास (तों) को टेम्प्लेट पैरामीटर (एस) के रूप में लेना शुरू किया जाता है, तो इसमें काफी अंतर होता है:

struct None {};

template<typename... Interfaces>
struct B : public Interfaces
{
    void hello() { ... }
};

struct A {
    virtual void hello() = 0;
};

template<typename... Interfaces>
void t_hello(const B<Interfaces...>& b) // different code generated for each set of interfaces (a vtable-based clever compiler might reduce this to 2); both t_hello and b.hello() might be inlined properly
{
    b.hello();   // indirect, non-virtual call
}

void hello(const A& a)
{
    a.hello();   // Indirect virtual call, inlining is impossible in general
}

int main()
{
    B<None>  b;         // Ok, no vtable generated, empty base class optimization works, sizeof(b) == 1 usually
    B<None>* pb = &b;
    B<None>& rb = b;

    b.hello();          // direct call
    pb->hello();        // pb-relative non-virtual call (1 redirection)
    rb->hello();        // non-virtual call (1 redirection unless optimized out)
    t_hello(b);         // works as expected, one redirection
    // hello(b);        // compile-time error


    B<A>     ba;        // Ok, vtable generated, sizeof(b) >= sizeof(void*)
    B<None>* pba = &ba;
    B<None>& rba = ba;

    ba.hello();         // still can be a direct call, exact type of ba is deducible
    pba->hello();       // pba-relative virtual call (usually 3 redirections)
    rba->hello();       // rba-relative virtual call (usually 3 redirections unless optimized out to 2)
    //t_hello(b);       // compile-time error (unless you add support for const A& in t_hello as well)
    hello(ba);
}

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

ध्यान दें कि, यदि आप ऐसा करते हैं, तो आप प्रतिलिपि बनाने / निर्माण करने वालों को टेम्पलेट के रूप में घोषित करना चाह सकते हैं, भी: विभिन्न इंटरफेस से निर्माण करने की अनुमति आपको विभिन्न B<>प्रकारों के बीच 'कास्ट' करने की अनुमति देती है ।

यह है कि क्या आप के लिए समर्थन जोड़ने चाहिए संदिग्ध है const A&में t_hello()। इस पुनर्लेखन के लिए सामान्य कारण वंशानुक्रम-आधारित विशेषज्ञता से हटकर खाका-आधारित एक है, ज्यादातर प्रदर्शन कारणों से। यदि आप पुराने इंटरफ़ेस का समर्थन करना जारी रखते हैं, तो आप पुराने उपयोग का शायद ही पता लगा सकते हैं (या इससे रोक सकते हैं)।


1

virtualकीवर्ड एक आधार वर्ग के कार्यों में जोड़ा जाना चाहिए उन्हें overridable बनाने के लिए। आपके उदाहरण में, struct Aआधार वर्ग है। virtualएक व्युत्पन्न वर्ग में उन कार्यों का उपयोग करने के लिए कुछ भी नहीं है। हालाँकि, यह आप चाहते हैं कि आपका व्युत्पन्न वर्ग भी एक आधार वर्ग ही हो, और आप चाहते हैं कि यह कार्य अति-उपयोगी हो, तो आपको virtualवहाँ लगाना होगा।

struct B : public A {
    virtual void hello() { ... }
};

struct C : public B {
    void hello() { ... }
};

यहाँ से Cविरासत मिली है B, इसलिए Bआधार वर्ग नहीं है (यह भी एक व्युत्पन्न वर्ग है), और Cव्युत्पन्न वर्ग है। विरासत चित्र इस तरह दिखता है:

A
^
|
B
^
|
C

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

इसलिए virtualवंशानुक्रम में शामिल सभी वर्गों के कार्यों के सामने रखें, जब तक कि आप यह सुनिश्चित न कर लें कि कक्षा में कोई भी बच्चा नहीं होगा, जिन्हें आधार वर्ग के कार्यों को ओवरराइड करने की आवश्यकता होगी। यह अच्छा अभ्यास है।


0

मैं निश्चित रूप से बच्चे के वर्ग के लिए वर्चुअल कीवर्ड शामिल करूंगा, क्योंकि

  • मैं। पठनीयता।
  • ii। यह चाइल्ड क्लास मेरी व्युत्पन्न हो गई है, आप नहीं चाहते हैं कि इस वर्चुअल फंक्शन को कॉल करने के लिए आगे व्युत्पन्न वर्ग का निर्माण हो।

1
मुझे लगता है कि उनका मतलब है कि बच्चे के कार्य को आभासी के रूप में चिह्नित किए बिना, एक प्रोग्रामर जो बाद में चाइल्ड क्लास से निकलता है, उसे यह महसूस नहीं हो सकता है कि फ़ंक्शन वास्तव में वर्चुअल है (क्योंकि उसने आधार वर्ग को कभी नहीं देखा था) और संभवतः निर्माण के दौरान इसे कॉल कर सकता है ( जो सही काम करे या न करे)।
PfhorSlayer
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.