क्या वर्चुअल फ़ंक्शंस में डिफ़ॉल्ट पैरामीटर हो सकते हैं?


164

यदि मैं एक बेस क्लास (या इंटरफ़ेस क्लास) घोषित करता हूं और इसके एक या अधिक मापदंडों के लिए डिफ़ॉल्ट मान निर्दिष्ट करता हूं, तो क्या व्युत्पन्न वर्गों को समान डिफॉल्ट्स निर्दिष्ट करना होगा और यदि नहीं, तो कौन सी डिफॉल्ट्स व्युत्पन्न वर्गों में प्रकट होंगी?

परिशिष्ट: मुझे इस बात में भी दिलचस्पी है कि इस परिदृश्य में विभिन्न संकलक और किसी भी इनपुट को "अनुशंसित" अभ्यास पर कैसे संभाला जा सकता है।


1
यह परीक्षण करने के लिए एक आसान बात लगती है। या तुमने कोशिश की?
औरंद

22
मैं इसे आज़माने की प्रक्रिया में हूं लेकिन मुझे इस बात की ठोस जानकारी नहीं मिली है कि व्यवहार "परिभाषित" कैसे होगा इसलिए मैं अपने विशिष्ट संकलक के लिए एक उत्तर पाऊंगा लेकिन यह नहीं बताऊंगा कि क्या सभी संकलनकर्ता ऐसा ही करेंगे चीज़। मुझे अनुशंसित अभ्यास में भी दिलचस्पी है।
अर्नोल्ड स्पेंस

1
व्यवहार अच्छी तरह से परिभाषित किया गया है, और मुझे संदेह है कि आपको एक कंपाइलर मिलेगा जो गलत है (ठीक है, हो सकता है कि अगर आप gcc 1.x का परीक्षण करें, या VC ++ 1.0 या ऐसा कुछ)। अनुशंसित अभ्यास ऐसा करने के खिलाफ है।
जेरी कॉफिन

जवाबों:


213

वर्चुअल में चूक हो सकती है। बेस क्लास में चूक व्युत्पन्न वर्गों द्वारा विरासत में नहीं मिली हैं।

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

कुछ कंपाइलर कुछ अलग कर सकते हैं, लेकिन यह C ++ 03 और C ++ 11 मानक कहते हैं:

8.3.6.10:

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

struct A {
  virtual void f(int a = 7);
};
struct B : public A {
  void f(int a);
};
void m()
{
  B* pb = new B;
  A* pa = pb;
  pa->f(); //OK, calls pa->B::f(7)
  pb->f(); //error: wrong number of arguments for B::f()
}

यहाँ एक नमूना कार्यक्रम है जो यह प्रदर्शित करता है कि क्या चूक हुई है। मैं structयहां classकेवल संक्षिप्तता के लिए एस का उपयोग कर रहा हूं - classऔर structडिफ़ॉल्ट दृश्यता को छोड़कर लगभग हर तरह से समान हैं।

#include <string>
#include <sstream>
#include <iostream>
#include <iomanip>

using std::stringstream;
using std::string;
using std::cout;
using std::endl;

struct Base { virtual string Speak(int n = 42); };
struct Der : public Base { string Speak(int n = 84); };

string Base::Speak(int n) 
{ 
    stringstream ss;
    ss << "Base " << n;
    return ss.str();
}

string Der::Speak(int n)
{
    stringstream ss;
    ss << "Der " << n;
    return ss.str();
}

int main()
{
    Base b1;
    Der d1;

    Base *pb1 = &b1, *pb2 = &d1;
    Der *pd1 = &d1;
    cout << pb1->Speak() << "\n"    // Base 42
        << pb2->Speak() << "\n"     // Der 42
        << pd1->Speak() << "\n"     // Der 84
        << endl;
}

इस कार्यक्रम का उत्पादन (MSVC10 और GCC 4.4 पर) है:

Base 42
Der 42
Der 84

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

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

मैं gcc 4.8.1 का उपयोग कर रहा हूं और मुझे एक संकलन त्रुटि "गलत संख्या का तर्क" नहीं मिलता है !!! बग खोजने के लिए मुझे डेढ़ दिन का समय लगा ...
स्टेफेन

2
लेकिन क्या इसका कोई कारण है? यह स्थिर प्रकार द्वारा क्यों निर्धारित किया जाता है?
user1289

2
क्लैंग-टिड्डी आभासी तरीकों पर डिफ़ॉल्ट मापदंडों को कुछ अवांछित मानती है और उस बारे में चेतावनी जारी करती है: github.com/llvm-mirror/clang-tools-extra/blob/master/clang-tidy/…
मार्टिन पेका

38

यह हर्ब सटर के शुरुआती सप्ताह के पदों में से एक का विषय था ।

इस विषय पर वह जो पहली बात कहते हैं, वह नहीं है।

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

दिया हुआ

class A {
    virtual void foo(int i = 1) { cout << "A::foo" << i << endl; }
};
class B: public A {
    virtual void foo(int i = 2) { cout << "B::foo" << i << endl; }
};
void test() {
A a;
B b;
A* ap = &b;
a.foo();
b.foo();
ap->foo();
}

आपको A :: foo1 B :: foo2 B :: foo1 मिलना चाहिए


7
धन्यवाद। हर्ब सटर से एक "ऐसा मत करो" कुछ वजन वहन करती है।
अर्नोल्ड स्पेंस

2
@ArnoldSpence, वास्तव में हर्ब सटर इस सिफारिश से परे है। उनका मानना ​​है कि एक इंटरफ़ेस में वर्चुअल तरीके नहीं होने चाहिए: gotw.ca/publications/mill18.htm । एक बार जब आपके तरीके ठोस होते हैं और ओवरराइड नहीं किया जा सकता है, तो उन्हें डिफ़ॉल्ट पैरामीटर देना सुरक्षित होता है।
मार्क रैनसम

1
मेरा मानना है कि वह क्या मतलब "ऐसा नहीं करते हैं कि " था तरीकों अधिभावी में "डिफ़ॉल्ट पैरामीटर का डिफ़ॉल्ट मान में परिवर्तन नहीं करते", नहीं "आभासी तरीकों में डिफ़ॉल्ट पैरामीटर निर्दिष्ट नहीं है"
Weipeng एल

6

यह एक बुरा विचार है, क्योंकि आपको मिलने वाले डिफ़ॉल्ट तर्क स्थिर प्रकार की वस्तु पर निर्भर करते हैं , जबकि virtualफ़ंक्शन को प्रेषित करने के लिए गतिशील पर निर्भर करेगा प्रकार ।

यह कहना है, जब आप किसी फ़ंक्शन को डिफ़ॉल्ट तर्कों के साथ कॉल करते हैं, तो डिफ़ॉल्ट तर्कों को संकलन समय पर प्रतिस्थापित किया जाता है, भले ही फ़ंक्शन क्या हो virtual या न हो।

@cppcoder ने अपने [बंद] प्रश्न में निम्नलिखित उदाहरण प्रस्तुत किया :

struct A {
    virtual void display(int i = 5) { std::cout << "Base::" << i << "\n"; }
};
struct B : public A {
    virtual void display(int i = 9) override { std::cout << "Derived::" << i << "\n"; }
};

int main()
{
    A * a = new B();
    a->display();

    A* aa = new A();
    aa->display();

    B* bb = new B();
    bb->display();
}

जो निम्नलिखित उत्पादन का उत्पादन करता है:

Derived::5
Base::5
Derived::9

उपरोक्त स्पष्टीकरण की सहायता से, यह देखना आसान है कि क्यों। संकलन के समय, संकलक स्थिर प्रकार के संकेत के सदस्य कार्यों से डिफ़ॉल्ट तर्कों को प्रतिस्थापित करता है, जिससे mainफ़ंक्शन निम्न के बराबर हो जाता है:

    A * a = new B();
    a->display(5);

    A* aa = new A();
    aa->display(5);

    B* bb = new B();
    bb->display(9);

4

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

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


1
यह बिल्कुल जटिल नहीं है। नाम रिज़ॉल्यूशन के साथ डिफ़ॉल्ट पैरामीटर खोजे जाते हैं। वे समान नियमों का पालन करते हैं।
एडवर्ड स्ट्रेंज

4

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

#include <iostream>

struct base { 
    virtual void x(int a=0) { std::cout << a; }
    virtual ~base() {}
};

struct derived1 : base { 
    void x(int a) { std:: cout << a; }
};

struct derived2 : base { 
    void x(int a = 1) { std::cout << a; }
};

int main() { 
    base *b[3];
    b[0] = new base;
    b[1] = new derived1;
    b[2] = new derived2;

    for (int i=0; i<3; i++) {
        b[i]->x();
        delete b[i];
    }

    derived1 d;
    // d.x();       // won't compile.
    derived2 d2;
    d2.x();
    return 0;
}

4
@ मन: [ध्यान से मासूम लग रही है] क्या लीक? :-)
जेरी कॉफ़िन

मुझे लगता है कि वह एक आभासी विध्वंसक की कमी की बात कर रहा है। लेकिन इस मामले में यह लीक नहीं होगा।
जॉन डिबलिंग

1
@ जेरी, डिस्ट्रक्टर वर्चुअल हो गया है यदि आप बेस क्लास पॉइंटर के बावजूद व्युत्पन्न वस्तु को हटा रहे हैं। अन्यथा बेस क्लास डिस्ट्रक्टर को इन सभी के लिए बुलाया जाएगा। इसमें यह ठीक है क्योंकि कोई विध्वंसक नहीं है। :-)
छप्पड़

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

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

4

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

इसलिए इसके बजाय,

//bad idea
virtual method1(int x = 0, int y = 0, int z = 0)

यह करो,

//good idea
struct Param1 {
  int x = 0, y = 0, z = 0;
};
virtual method1(const Param1& p)
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.