2009-06-09 8 views
21

वर्चुअल असाइनमेंट ऑपरेटर को लागू करने के साथ खेलते समय मैंने एक मजेदार व्यवहार के साथ समाप्त कर दिया है। यह एक कंपाइलर गड़बड़ नहीं है, क्योंकि जी ++ 4.1, 4.3 और वीएस 2005 एक ही व्यवहार साझा करते हैं।वर्चुअल असाइनमेंट समान हस्ताक्षर के अन्य आभासी कार्यों की तुलना में अलग तरीके से क्यों व्यवहार करता है?

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

struct Base { 
    virtual Base& f(Base const &) { 
     std::cout << "Base::f(Base const &)" << std::endl; 
     return *this; 
    } 
    virtual Base& operator=(Base const &) { 
     std::cout << "Base::operator=(Base const &)" << std::endl; 
     return *this; 
    } 
}; 
struct Derived : public Base { 
    virtual Base& f(Base const &) { 
     std::cout << "Derived::f(Base const &)" << std::endl; 
     return *this; 
    } 
    virtual Base& operator=(Base const &) { 
     std::cout << "Derived::operator=(Base const &)" << std::endl; 
     return *this; 
    } 
}; 
int main() { 
    Derived a, b; 

    a.f(b); // [0] outputs: Derived::f(Base const &) (expected result) 
    a = b; // [1] outputs: Base::operator=(Base const &) 

    Base & ba = a; 
    Base & bb = b; 
    ba = bb; // [2] outputs: Derived::operator=(Base const &) 

    Derived & da = a; 
    Derived & db = b; 
    da = db; // [3] outputs: Base::operator=(Base const &) 

    ba = da; // [4] outputs: Derived::operator=(Base const &) 
    da = ba; // [5] outputs: Derived::operator=(Base const &) 
} 

प्रभाव है कि आभासी ऑपरेटर = समान हस्ताक्षर से किसी अन्य आभासी समारोह की तुलना में एक अलग व्यवहार किया है ([0] [1] की तुलना में), जब कहा जाता है के माध्यम से ऑपरेटर की बेस संस्करण को फोन करके असली व्युत्पन्न वस्तुएं ([1]) या व्युत्पन्न संदर्भ ([3]) जबकि यह बेस संदर्भ ([2]) के माध्यम से कॉल किए जाने पर नियमित वर्चुअल फ़ंक्शन के रूप में कार्य करता है, या जब या तो लालसा या रावल्यू मूल संदर्भ हैं और दूसरा व्युत्पन्न संदर्भ ([4], [5])।

क्या इस अजीब व्यवहार के लिए कोई समझदार स्पष्टीकरण है?

उत्तर

13

यहाँ यह कैसे जाता है:

अगर मैं [1] को

a = *((Base*)&b); 

तो चीजें जिस तरह से आप उम्मीद कर काम बदल जाते हैं।

Derived& operator=(Derived const & that) { 
    Base::operator=(that); 
    // rewrite all Derived members by using their assignment operator, for example 
    foo = that.foo; 
    bar = that.bar; 
    return *this; 
} 

अपने उदाहरण compilers में अनुमान लगाना कि a और b प्रकार Derived के हैं पर्याप्त जानकारी है और इसलिए वे कि कॉल ऊपर स्वतः उत्पन्न ऑपरेटर का उपयोग करने के लिए चुन: वहाँ Derived में एक स्वत: उत्पन्न असाइनमेंट ऑपरेटर है कि इस तरह दिखाई देता है आपका अपना। इस तरह आपको मिला [1]। मेरे पॉइंटर कास्टिंग कंपलर को आपके तरीके से करने के लिए मजबूर करता है, क्योंकि मैं कंपाइलर को "भूलना" कहता हूं कि bDerived प्रकार है और इसलिए यह Base का उपयोग करता है।

अन्य परिणाम उसी तरह समझाया जा सकता है।

+3

कोई यहाँ शामिल अनुमान लगा है। नियम बहुत सख्त हैं। – MSalters

+0

धन्यवाद, वास्तविक उत्तर (जैसा कि पहले से ही तीन लोगों द्वारा पोस्ट किया गया है) यह है कि संकलित जेनरेटर ऑपरेटर = व्युत्पन्न वर्ग के लिए बेस :: ऑपरेटर = को स्पष्ट रूप से कॉल करता है। मैं इसे 'स्वीकृत उत्तर' के रूप में चिह्नित कर रहा हूं क्योंकि यह पहला था। –

+0

'a = static_cast <बेस &>(b); 'सी-स्टाइल कास्ट से बचने का एक तरीका होगा (जो गलती से एक बार फिर से डालने का जोखिम लेता है) –

4

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

virtual Base& operator=(Base const &) //is not assignment operator for Derived 

इसलिए, a = b; // [1] outputs: Base::operator=(Base const &)

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

ba = bb; // [2] outputs: Derived::operator=(Base const &) 

==> आंतरिक ==> (वस्तु> vtable [Assignement ऑपरेटर]) वर्ग जो करने के लिए वस्तु अंतर्गत आता है की vtable में असाइनमेंट ऑपरेटर के लिए प्रवेश प्राप्त करें और विधि आह्वान।

3

यदि आप उपयुक्त operator= (यानी सही वापसी और तर्क प्रकार) प्रदान करने में विफल रहते हैं, तो डिफ़ॉल्ट operator= संकलक द्वारा प्रदान किया जाता है जो किसी भी उपयोगकर्ता द्वारा परिभाषित एक ओवरलोड करता है। आपके मामले में यह व्युत्पन्न सदस्यों की प्रतिलिपि बनाने से पहले Base::operator= (Base const&) पर कॉल करेगा।

ऑपरेटर = विवरण वर्चुअल होने पर विवरण के लिए यह link देखें।

5

वहाँ तीन ऑपरेटर = इस मामले में कर रहे हैं:

Base::operator=(Base const&) // virtual 
Derived::operator=(Base const&) // virtual 
Derived::operator=(Derived const&) // Compiler generated, calls Base::operator=(Base const&) directly 

यही कारण है यह बेस :: ऑपरेटर = (बेस स्थिरांक &) की तरह दिखता है कहा जाता है "वस्तुतः" मामले में [1]। इसे कंपाइलर से उत्पन्न संस्करण से कहा जाता है। यह वही लागू होता है [3]। 2 मामले में, दाएं हाथ की ओर तर्क 'बीबी' में बेस & टाइप किया गया है, इसलिए व्युत्पन्न :: ऑपरेटर = (व्युत्पन्न &) नहीं कहा जा सकता है।

2

संकलक होने का कारण डिफ़ॉल्ट असाइनमेंट operator= प्रदान किया गया है। परिदृश्य में a = b और जिसे हम डिफ़ॉल्ट रूप से बेस असाइनमेंट ऑपरेटर कहते हैं, डिफ़ॉल्ट रूप से जानते हैं। आभासी असाइनमेंट के बारे में

अधिक विवरण में पाया जा सकता: https://stackoverflow.com/a/26906275/3235055

संबंधित मुद्दे