2015-06-01 9 views
7

मैं आभासी विरासत की अवधारणा को बेहतर ढंग से समझने की कोशिश कर रहा हूं, और इसके खतरे क्या हैं।वर्चुअल विरासत कन्स्ट्रक्टर ऑर्डर

मैंने एक और पोस्ट (Why is Default constructor called in virtual inheritance?) में पढ़ा है कि यह (= वर्चुअल विरासत) कन्स्ट्रक्टर कॉल के क्रम को बदलता है ("दादी" को पहले कहा जाता है, जबकि वर्चुअल विरासत के बिना यह नहीं होता है)।

#define tracefunc printf(__FUNCTION__); printf("\r\n") 
struct A 
{ 
    A(){ tracefunc; } 

}; 

struct B1 : public A 
{ 
    B1(){ tracefunc; }; 
}; 

struct B2 : virtual public A 
{ 
    B2() { tracefunc; }; 
}; 

struct C1 : public B1 
{ 
    C1() { tracefunc; }; 
}; 

struct C2 : virtual public B2 
{ 
    C2() { tracefunc; }; 
}; 

int _tmain(int argc, _TCHAR* argv[]) 
{ 
    A* pa1 = new C1(); 
    A* pa2 = new C2(); 
} 

उत्पादन होता है:

तो मैं यह देखने के लिए कि मैं विचार (VS2013) मिला निम्नलिखित की कोशिश की

A::A 
B1::B1 
C1::C1 
A::A 
B2::B2 
C2::C2 

कौन सा नहीं है कि मैं क्या उम्मीद (मैं के आदेश की उम्मीद 2 कक्षाएं अलग होंगी)।

मुझे क्या याद आ रही है? क्या कोई मुझे ऐसे स्रोत को समझा सकता है या निर्देशित कर सकता है जो इसे बेहतर समझाता है?

धन्यवाद!

+1

आपने क्या उम्मीद की थी? "दादी" हमेशा पहले बुलाया जाता है। इससे कोई फर्क नहीं पड़ता कि आप किस प्रकार की विरासत का उपयोग कर रहे हैं। यदि यह अलग था तो निम्न स्तर की कक्षाओं ने अपने रचनाकारों में माता-पिता के डेटा का उपयोग कैसे किया? – ixSci

+0

आभासी विरासत केवल एकाधिक विरासत के मामले में अंतर बनाती है। यह विरासत ग्राफ के माध्यम से कुछ संरचना साझा करने के लिए प्रदान किया जाता है। –

उत्तर

2

आपके उदाहरण में, आपके आउटपुट की अपेक्षा की जा सकती है। Virtual inheritance उदाहरण में खेलता है जब आपके पास एकाधिक विरासत वाले वर्ग होते हैं, जिनके माता-पिता वर्ग भी उसी वर्ग/प्रकार (यानी "हीरा समस्या") से प्राप्त होते हैं। आपके उदाहरण में, आपकी कक्षाओं को वर्चुअल रूप से प्राप्त करने के लिए सेट किया जा सकता है (यदि कोड में कहीं और आवश्यक है), लेकिन वे आपके उदाहरण के आधार पर 'वर्चुअल वारिस' जरूरी नहीं हैं क्योंकि व्युत्पन्न कक्षाओं में से कोई भी नहीं (B1/B2/C1/C2) सीधे प्राप्तकर्ता से अधिक करता है A से।

का विस्तार करने के लिए, मैं अपने उदाहरण बदलाव किया है थोड़ा और व्याख्या करने के लिए:

#include <cstdio> 

#define tracefunc printf(__FUNCTION__); printf("\r\n") 
struct A 
{ 
    A() { tracefunc; } 
    virtual void write() { tracefunc; } 
    virtual void read() { tracefunc; } 
}; 

struct B1 : public A 
{ 
    B1() { tracefunc; }; 
    void read(){ tracefunc; } 
}; 

struct C1 : public A 
{ 
    C1() { tracefunc; }; 
    void write(){ tracefunc; } 
}; 

struct B2 : virtual public A 
{ 
    B2() { tracefunc; }; 
    void read(){ tracefunc; } 
}; 

struct C2 : virtual public A 
{ 
    C2() { tracefunc; }; 
    void write(){ tracefunc; } 
}; 

// Z1 inherits from B1 and C1, both of which inherit from A; when a call is made to any 
// of the base function (i.e. A::read or A::write) from the derived class, the call is 
// ambiguous since B1 and C1 both have a 'copy' (i.e. vtable) for the A parent class. 
struct Z1 : public B1, public C1 
{ 
    Z1() { tracefunc; } 
}; 

// Z2 inherits from B2 and C2, both of which inherit from A virtually; note that Z2 doesn't 
// need to inherit virtually from B2 or C2. Since B2 and C2 both virtual inherit from A, when 
// they are constructed, only 1 copy of the base A class is made and the vtable pointer info 
// is "shared" between the 2 base objects (B2 and C2), and the calls are no longer ambiguous 
struct Z2 : public B2, public C2 
{ 
    Z2() { tracefunc; } 
}; 


int _tmain(int argc, _TCHAR* argv[]) 
{ 
    // gets 2 "copies" of the 'A' base since 'B1' and 'C1' don't virtually inherit from 'A' 
    Z1 z1; 
    // gets only 1 "copy" of 'A' base since 'B2' and 'C2' virtualy inherit from 'A' and thus "share" the vtable pointer to the 'A' base 
    Z2 z2; 

    z1.write(); // ambiguous call to write (which one is it .. B1::write() (since B1 inherits from A) or A::write() ?) 
    z1.read(); // ambiguous call to read (which one is it .. C1::read() (since C1 inherits from A) or A::read() ?) 

    z2.write(); // not ambiguous: z2.write() calls C2::write() since it's "virtually mapped" to/from A::write() 
    z2.read(); // not ambiguous: z2.read() calls B2::read() since it's "virtually mapped" to/from A::read() 
    return 0; 
} 

यह हमें मनुष्य जो फोन हम z1 चर के मामले में करने का इरादा करने के लिए "स्पष्ट" हो सकती है, चूंकि B1 में write विधि नहीं है, तो मैं संकलक को C1::write विधि चुनने की अपेक्षा करता हूं, लेकिन ऑब्जेक्ट्स की मेमोरी मैपिंग कैसे काम करती है, यह C1 ऑब्जेक्ट में A की मूल प्रतिलिपि के कारण समस्या उत्पन्न करती है B1 ओबीजे में A आधार की प्रति से अलग जानकारी (पॉइंटर्स/संदर्भ/हैंडल) ect (चूंकि तकनीकी रूप से A आधार की 2 प्रतियां हैं); इस प्रकार B1::read() { this->write(); } पर एक कॉल अप्रत्याशित व्यवहार दे सकता है (हालांकि अपरिभाषित नहीं)।

virtual बेस क्लास विनिर्देशक पर कीवर्ड यह स्पष्ट करता है कि अन्य वर्ग जो समान आधार प्रकार से प्राप्त होते हैं, केवल मूल प्रकार की 1 प्रति प्राप्त करेंगे।

ध्यान दें कि उपर्युक्त कोड z1 ऑब्जेक्ट के लिए संदिग्ध कॉल समझाते हुए संकलक त्रुटियों के साथ संकलित करने में विफल होना चाहिए। ,

A::A 
B1::B1 
A::A 
C1::C1 
Z1::Z1 
A::A 
B2::B2 
C2::C2 
Z2::Z2 
C2::write 
B2::read 

नोट 2 Z1 से पहले A ctor (A::A) के लिए कॉल का निर्माण किया है, जबकि Z2 केवल 1 कॉल है: आप z1.write(); और z1.read(); लाइनों उत्पादन बाहर टिप्पणी है (मेरे लिए कम से कम) निम्नलिखित है A कन्स्ट्रक्टर के लिए।

मैं the following on virtual inheritance पढ़ने की अनुशंसा करता हूं क्योंकि यह ध्यान देने के लिए कुछ अन्य दोषों पर गहराई से अधिक जाता है (इस तथ्य की तरह कि वर्चुअल विरासत वाले वर्गों को बेस क्लास सीटीओआर कॉल करने के लिए प्रारंभिक सूची का उपयोग करने की आवश्यकता है, या आपको टालना चाहिए इस प्रकार की विरासत करते समय सी-शैली का उपयोग करता है)।

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

आशा है कि चीजों को थोड़ा सा साफ़ करने में मदद कर सकते हैं।

0

आप उत्पादन में कोई अंतर देखने के लिए क्योंकि उत्पादन निम्नलिखित वर्ग hiearchies में से किसी में एक ही होगा नहीं कर सकेंगे:

hiearchy 1

class A {}; 

class B2 : virtual public A {}; 

class C2 : virtual public B2 {}; 

hiearchy 2

class A {}; 

class B2 : public A {}; 

class C2 : virtual public B2 {}; 

hiearchy 3

class A {}; 

class B2 : virtual public A {}; 

class C2 : public B2 {}; 

hiearchy 3

class A {}; 

class B2 : public A {}; 

class C2 : public B2 {}; 

इन सभी मामले में, A::A() पहले निष्पादित किया जाएगा, B2::B2() द्वारा पीछा किया, और फिर C2::C2()

उनके बीच का अंतर यह है कि A::A() कहलाता है। क्या इसे B2::B2(), या C2::C2() से कॉल किया जाता है?

मैं के उत्तर पर 100% स्पष्ट नहीं हूं Hiearchy 1। मुझे लगता है कि B2::B2()C2::C2 से B2C की वर्चुअल बेस क्लास के बाद से कॉल किया जाना चाहिए। A::A() को B2:B2() से कॉल किया जाना चाहिए क्योंकि AB2 का वर्चुअल बेस क्लास है। लेकिन मैं सही क्रम पर गलत हो सकता था।

में पदानुक्रम 2, A::A()B2::B2() से कॉल किया जाएगा। चूंकि B2virtualC2 का बेस क्लास B2::B2()C2::C2() से कॉल किया जाता है। चूंकि AB2 का सामान्य आधार वर्ग है, A::A()B2::B2() से कॉल किया जाता है।

में पदानुक्रम 2, A::A()C2::C2() से कॉल किया जाएगा। चूंकि A वर्चुअल बेस क्लास है, A::A()C2::C2() से कॉल किया जाता है। B2::B2() को कॉल करने के बाद कॉल किया जाता है A::A() पूरा हो गया है।

में पदानुक्रम 4, A::A()B2::B2() से कॉल किया जाएगा। मुझे लगता है कि इस मामले को कोई स्पष्टीकरण की जरूरत नहीं है।

#include <iostream> 

class A 
{ 
    public: 
     A(char const *from) { std::cout << "Called from : " << from << std::endl; } 

}; 

class B2 : virtual public A 
{ 
    public: 
     B2() : A("B2::B2()") {} 
}; 

class C2 : virtual public B2 
{ 
    public: 
     C2() : A("C2::C2()") {} 
}; 

int main() 
{ 
    C2 c; 
} 

मैं निम्नलिखित उत्पादन मिल गया:

मेरी संदेह के बारे में hiearchy 1, मैं निम्नलिखित प्रोग्राम का उपयोग किया स्पष्ट करने के लिए

Called from : C2::C2() 

यह पुष्टि क्या @TC उसकी टिप्पणी में संकेत दिया, जो मैंने अपेक्षा की थी उससे अलग है। A::A()C2::C2 से B2::B2 से नहीं कहा जाता है।

+2

वर्चुअल बेस हमेशा सबसे व्युत्पन्न वर्ग के निर्माता द्वारा बनाए जाते हैं; उनका निर्माण आदेश आधार वर्गों के डीएजी की गहराई से पहले बाएं से दाएं ट्रैवर्सल है। इसलिए, 1 में, 'सी 2 :: सी 2() 'पहले' ए :: ए() 'कॉल करेगा, फिर' बी 2 :: बी 2()' पर कॉल करें। –

+0

@ टी.सी. जानकारी के लिए धन्यवाद। मैंने सत्यापित किया कि आप सही हैं। –

+0

... जहां "बाएं से दाएं" कोड में उपस्थिति के क्रम को संदर्भित करता है, और ट्रैवर्सल _post-order_ है। – RJFalconer

1

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

A 
    | | 
    B C 
    | | 
    D 

तो डी बी और सी से दो उदाहरणों एक है एक आभासी कार्य करता है, तो: उदाहरण के लिए, बी ए से विरासत, सी एक से विरासत और डी बी से विरासत, सी आरेख इस तरह है , वहाँ एक समस्या है।

उदाहरण के लिए:

struct A 
{ 
    virtual void foo(){__builtin_printf("A");} 
    virtual void bar(){} 
}; 

struct B : A 
{ 
    virtual void foo(){__builtin_printf("B");} 
}; 

struct C : A 
{ 
    virtual void bar(){} 
}; 

struct D : B, C 
{ 

}; 

int main() 
{ 
    D d; 
    d.foo(); // Error 
} 

अगर मैं अपने XLC संकलक उपयोग संकलन और चलाने के लिए:

xlC -+ a.C 

त्रुटि संदेश की तरह है:

a.C:25:7: error: member 'foo' found in multiple base classes of different types 
    d.foo(); // Error 
    ^
a.C:9:18: note: member found by ambiguous name lookup 
    virtual void foo(){__builtin_printf("B");} 
       ^
a.C:3:18: note: member found by ambiguous name lookup 
    virtual void foo(){__builtin_printf("A");} 
       ^
1 error generated. 
Error while processing a.C. 

त्रुटि संदेश है बहुत स्पष्ट, सदस्य 'foo' विभिन्न प्रकार के कई आधार वर्गों में पाया जाता है। यदि हम आभासी विरासत जोड़ते हैं, तो समस्या हल हो जाती है।

A  A 
|  | 
B1 B2 
|  | 
C1 C2 

नहीं 'हीरा समस्या' नहीं है, इस: क्योंकि एक के निर्माण के अधिकार D से नियंत्रित किया जाता है, वहाँ ए

अपने कोड पर वापस का एक उदाहरण है, विरासत आरेख इस तरह है केवल एक विरासत है। इसलिए, निर्माण आदेश भी ए-> बी 2-> सी 2 है, आउटपुट का कोई अंतर नहीं है।

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