व्युत्पन्न वर्ग में एक अतिरंजित कार्य आधार वर्ग के अन्य अधिभार को क्यों छिपाता है?


219

कोड पर विचार करें:

#include <stdio.h>

class Base {
public: 
    virtual void gogo(int a){
        printf(" Base :: gogo (int) \n");
    };

    virtual void gogo(int* a){
        printf(" Base :: gogo (int*) \n");
    };
};

class Derived : public Base{
public:
    virtual void gogo(int* a){
        printf(" Derived :: gogo (int*) \n");
    };
};

int main(){
    Derived obj;
    obj.gogo(7);
}

यह त्रुटि मिली:

> जी ++ -ऑप्टिक-ओट्स टेस्ट.कैप-ओ टेस्ट
test.cpp: फ़ंक्शन में 'int main ()':
test.cpp: 31: त्रुटि: कॉल के लिए कोई मिलान कार्य `व्युत्पन्न :: गोगो (int) 'के लिए नहीं
test.cpp: 21: नोट: उम्मीदवार हैं: आभासी शून्य व्युत्पन्न :: गोगो (int *) 
test.cpp: 33: 2: चेतावनी: फ़ाइल के अंत में कोई नई पंक्ति नहीं
> बाहर निकलें कोड: 1

यहाँ, व्युत्पन्न वर्ग का कार्य आधार वर्ग में एक ही नाम (हस्ताक्षर नहीं) के सभी कार्यों को ग्रहण कर रहा है। किसी तरह, C ++ का यह व्यवहार ठीक नहीं लगता है। बहुरूपी नहीं।


1
डुप्लिकेट: stackoverflow.com/questions/411103/…
psychotik

8
शानदार सवाल, मैंने केवल इसे हाल ही में भी खोजा
मैट जॉइनर

11
मुझे लगता है कि बज्ने (लिंक मैक द्वारा पोस्ट की गई) ने इसे एक वाक्य में सबसे अच्छा रखा: "सी ++ में, स्कॉप्स में कोई ओवरलोडिंग नहीं है - व्युत्पन्न वर्ग स्कोप इस सामान्य नियम के अपवाद नहीं हैं।"
शिवबाबू

7
@ आशीष वह लिंक टूट गया है। यहाँ सही है (अब के रूप में) - stroustrup.com/bs_faq2.html#overloadderived
nsane

3
इसके अलावा, यह इंगित करना चाहता था कि obj.Base::gogo(7);अभी भी छिपे हुए फ़ंक्शन को कॉल करके काम करता है।
फोरम्यूलेटर

जवाबों:


406

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

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

उदाहरण के लिए, मान लें कि आधार वर्ग Bका सदस्य कार्य हैfoo जो प्रकार का पैरामीटर लेता है void *, और सभी कॉल को foo(NULL)हल किया जाता है B::foo(void *)। मान लीजिए कि कोई नाम नहीं छिपा है और यह B::foo(void *)कई अलग-अलग वर्गों में दिखाई दे रहा है B। हालाँकि, मान लें कि कुछ [अप्रत्यक्ष, दूरस्थ] Dवर्ग के वंशज Bएक फ़ंक्शन foo(int)परिभाषित है। अब, बिना नाम छिपाने Dके दोनों foo(void *)और foo(int)दिखाई देते हैं और अधिभार संकल्प में भाग लेते हैं। यदि किस foo(NULL)प्रकार की वस्तु के माध्यम से कॉल किया जाता है, तो किस फ़ंक्शन को हल करना होगा D? वे करने का संकल्प लेंगे D::foo(int), क्योंकि intअभिन्न शून्य (यानी) के लिए एक बेहतर मैच हैNULL) किसी भी सूचक प्रकार से। इसलिए, पूरे पदानुक्रम में foo(NULL)एक फ़ंक्शन को हल करने के लिए कॉल किया जाता है , जबकि D(और अंडर में) वे अचानक दूसरे को हल करते हैं।

एक और उदाहरण C ++ के डिजाइन और विकास में दिया गया है , पृष्ठ 77:

class Base {
    int x;
public:
    virtual void copy(Base* p) { x = p-> x; }
};

class Derived{
    int xx;
public:
    virtual void copy(Derived* p) { xx = p->xx; Base::copy(p); }
};

void f(Base a, Derived b)
{
    a.copy(&b); // ok: copy Base part of b
    b.copy(&a); // error: copy(Base*) is hidden by copy(Derived*)
}

इस नियम के बिना, बी की स्थिति आंशिक रूप से अपडेट की जाएगी, जिससे स्लाइसिंग हो सकती है।

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

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


22
हां, यह प्रश्न का वास्तविक उत्तर है। धन्यवाद। मैं भी उत्सुक था।
सर्वव्यापी

4
बहुत बढ़िया जवाब! इसके अलावा, एक व्यावहारिक बात के रूप में, संकलन शायद बहुत धीमी हो जाएगी यदि नाम खोज को हर बार शीर्ष पर जाना था।
ड्रू हॉल

6
(पुराना उत्तर, मुझे पता है।) अब nullptrमैं यह कहकर आपके उदाहरण पर आपत्ति करूंगा कि "यदि आप void*संस्करण को कॉल करना चाहते हैं , तो आपको एक पॉइंटर प्रकार का उपयोग करना चाहिए"। क्या एक अलग उदाहरण है जहां यह खराब हो सकता है?
GManNickG

3
नाम छिपाना वास्तव में बुराई नहीं है। "Is-a" संबंध अभी भी है, और आधार इंटरफ़ेस के माध्यम से उपलब्ध है। तो शायद d->foo()आप मिल जाएगा नहीं "है-एक Base", लेकिन static_cast<Base*>(d)->foo() होगा गतिशील प्रेषण भी शामिल है।
केरेके एसबी

12
यह उत्तर अस्वाभाविक है क्योंकि दिए गए उदाहरण को छिपाने के साथ या उसके बिना समान व्यवहार होता है: D :: foo (int) को या तो इसलिए बुलाया जाएगा क्योंकि यह एक बेहतर मैच है या क्योंकि इसमें B: foo (int) छिपा हुआ है।
रिचर्ड वुल्फ

46

नाम रिज़ॉल्यूशन नियम कहते हैं कि नाम लुकअप पहले स्कोप में रुकता है जिसमें एक मिलान नाम मिलता है। उस समय, ओवरलोड रिज़ॉल्यूशन उपलब्ध कार्यों के सर्वश्रेष्ठ मिलान को खोजने के लिए किक करता है।

इस मामले gogo(int*)में, व्युत्पन्न वर्ग के दायरे में पाया (अकेला) है, और जैसा कि इंट से इंट * तक कोई मानक रूपांतरण नहीं है, लुकअप विफल हो जाता है।

इसका समाधान यह है कि व्युत्पन्न वर्ग में आधार घोषणा के माध्यम से आधार घोषणाओं को लाया जाए:

using Base::gogo;

... नाम देखने के नियमों को सभी उम्मीदवारों को खोजने की अनुमति देगा और इस प्रकार अधिभार संकल्प आपकी अपेक्षा के अनुसार आगे बढ़ेगा।


10
ओपी: "व्युत्पन्न वर्ग में एक अतिरंजित कार्य आधार वर्ग के अन्य अधिभार को क्यों छिपाता है?" यह उत्तर: "क्योंकि यह करता है"।
रिचर्ड वुल्फ

12

यह "बाय डिजाइन" है। इस प्रकार की विधि के लिए C ++ अधिभार संकल्प में निम्नलिखित की तरह काम करता है।

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

चूंकि डेरेव्ड में "गोगो" नाम का एक मिलान फ़ंक्शन नहीं है, इसलिए ओवरलोड रिज़ॉल्यूशन विफल हो जाता है।


2

नाम छिपाना समझ में आता है क्योंकि यह नाम संकल्प में अस्पष्टता को रोकता है।

इस कोड पर विचार करें:

class Base
{
public:
    void func (float x) { ... }
}

class Derived: public Base
{
public:
    void func (double x) { ... }
}

Derived dobj;

यदि Derived Base::func(float)द्वारा छिपाया नहीं गया था Derived::func(double), तो हम कॉल करते समय बेस क्लास फ़ंक्शन को कॉल करेंगे dobj.func(0.f), भले ही एक फ्लोट को डबल में पदोन्नत किया जा सकता है।

संदर्भ: http://bastian.rieck.ru/blog/posts/2016/name_hiding_cxx/

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