वर्चुअल इनहेरिटेंस
आभासी विरासत एक सी ++ तकनीक है जो यह सुनिश्चित करती है कि आधार वर्ग की केवल एक प्रति सुनिश्चित करती है{{'}ग्रैंडचिल्ड व्युत्पन्न कक्षाओं द्वारा } सदस्य चर वंशानुक्रम (कंप्यूटर विज्ञान) हैं। आभासी विरासत के बिना, यदि दो वर्ग B
और C
एक वर्ग से विरासत में मिला A
, और एक वर्ग D
दोनों से विरासत में मिलता है B
और C
, तब D
की दो प्रतियाँ होंगी A
's सदस्य चर: एक के माध्यम से B
, और एक के माध्यम से C
. ये स्कोप रिज़ॉल्यूशन ऑपरेटर का उपयोग करके स्वतंत्र रूप से एक्सेस किए जा सकेंगे।
इसके बजाय, यदि कक्षाएं B
और C
वस्तुतः वर्ग से विरासत में मिलता है A
, फिर कक्षा की वस्तुएं D
कक्षा से सदस्य चर का केवल एक सेट होगा A
.
यह सुविधा एकाधिक वंशानुक्रम के लिए सबसे उपयोगी है, क्योंकि यह व्युत्पन्न वर्ग और इससे प्राप्त होने वाले सभी वर्गों के लिए वर्चुअल आधार को एक सामान्य सबोबिज बनाता है। इसका उपयोग हीरे की समस्या से बचने के लिए अस्पष्टता को स्पष्ट करने के लिए किया जा सकता है कि किस पूर्वज वर्ग का उपयोग किया जाए, जैसा कि व्युत्पन्न वर्ग के दृष्टिकोण से (D
ऊपर के उदाहरण में) आभासी आधार (A
) कार्य करता है जैसे कि यह प्रत्यक्ष आधार वर्ग था D
, आधार के माध्यम से अप्रत्यक्ष रूप से व्युत्पन्न वर्ग नहीं (B
या C
).[1][2]
इसका उपयोग तब किया जाता है जब वंशानुक्रम भागों की संरचना के बजाय सेट के प्रतिबंध का प्रतिनिधित्व करता है। सी ++ में, पूरे पदानुक्रम में आम होने का इरादा रखने वाली एक बेस क्लास को वर्चुअल के साथ चिह्नित किया जाता है virtual
कीवर्ड (कंप्यूटर प्रोग्रामिंग)।
निम्नलिखित वर्ग पदानुक्रम पर विचार करें।
struct Animal {
virtual ~Animal() = default;
virtual void Eat() {}
};
struct Mammal: Animal {
virtual void Breathe() {}
};
struct WingedAnimal: Animal {
virtual void Flap() {}
};
// A bat is a winged mammal
struct Bat: Mammal, WingedAnimal {};
Bat bat;
जैसा कि ऊपर घोषित किया गया है, को कॉल करें bat.Eat
अस्पष्ट है क्योंकि दो हैं Animal
(अप्रत्यक्ष) आधार वर्ग में Bat
, कोई भी Bat
वस्तु के दो भिन्न होते हैं Animal
बेस क्लास सबऑब्जेक्ट्स। तो एक संदर्भ को सीधे बाँधने का प्रयास Animal
ए का विषय Bat
वस्तु विफल हो जाएगी, क्योंकि बाध्यकारी स्वाभाविक अस्पष्ट है:
Bat b;
Animal& a = b; // error: which Animal subobject should a Bat cast into,
// a Mammal::Animal or a WingedAnimal::Animal?
असंबद्ध करने के लिए, किसी को स्पष्ट रूप से परिवर्तित करना होगा bat
या तो बेस क्लास सबऑब्जेक्ट के लिए:
Bat b;
Animal& mammal = static_cast<Mammal&>(b);
Animal& winged = static_cast<WingedAnimal&>(b);
कॉल करने के लिए Eat
, वही असंबद्धता, या स्पष्ट योग्यता आवश्यक है: static_cast<Mammal&>(bat).Eat()
या static_cast<WingedAnimal&>(bat).Eat()
या वैकल्पिक रूप से bat.Mammal::Eat()
और bat.WingedAnimal::Eat()
. स्पष्ट योग्यता न केवल पॉइंटर्स और ऑब्जेक्ट्स दोनों के लिए एक आसान, समान सिंटैक्स का उपयोग करती है, बल्कि स्थिर प्रेषण की भी अनुमति देती है, इसलिए यह यकीनन बेहतर तरीका होगा।
इस मामले में, की दोहरी विरासत Animal
शायद अवांछित है, जैसा कि हम मॉडल करना चाहते हैं कि संबंध (Bat
एक Animal
) केवल एक बार मौजूद होता है; कि एक Bat
एक है Mammal
और एक है WingedAnimal
इसका मतलब यह नहीं है कि यह एक है Animal
दो बार: ए Animal
बेस क्लास एक अनुबंध से मेल खाती है Bat
लागू करता है (ऊपर एक संबंध है जिसका वास्तव में मतलब है की आवश्यकताओं को लागू करता है), और a Bat
ही क्रियान्वित करता है Animal
अनुबंध एक बार। केवल एक बार का वास्तविक विश्व अर्थ है Bat
लागू करने का एक ही तरीका होना चाहिए Eat
, दो अलग-अलग तरीकों से नहीं, इस पर निर्भर करता है कि क्या Mammal
का दृश्य Bat
खा रहा है, या WingedAnimal
का दृश्य Bat
. (पहले कोड उदाहरण में हम देखते हैं Eat
दोनों में ओवरराइड नहीं किया गया है Mammal
या WingedAnimal
, तो दो Animal
सबोबजेक्ट्स वास्तव में वही व्यवहार करेंगे, लेकिन यह केवल एक अपमानजनक मामला है, और इससे सी ++ दृष्टिकोण से कोई फर्क नहीं पड़ता है।)
इस स्थिति को कभी-कभी डायमंड इनहेरिटेंस (डायमंड प्रॉब्लम देखें) कहा जाता है क्योंकि इनहेरिटेंस डायग्राम डायमंड के आकार में होता है। वर्चुअल इनहेरिटेंस इस समस्या को हल करने में मदद कर सकता है।
समाधान
हम अपनी कक्षाओं को निम्नानुसार पुनः घोषित कर सकते हैं:
struct Animal {
virtual ~Animal() = default;
virtual void Eat() {}
};
// Two classes virtually inheriting Animal:
struct Mammal: virtual Animal {
virtual void Breathe() {}
};
struct WingedAnimal: virtual Animal {
virtual void Flap() {}
};
// A bat is still a winged mammal
struct Bat: Mammal, WingedAnimal {};
Animal
ई> का हिस्साBat::WingedAnimal
अब वही हैAnimal
उदाहरण के रूप में द्वारा उपयोग किया जाता हैBat::Mammal
, कहने का तात्पर्य यह है कि एBat
केवल एक है, साझा किया,Animal
इसके प्रतिनिधित्व में उदाहरण और इसलिए एक कॉलBat::Eat
असंदिग्ध है। इसके अतिरिक्त, से एक सीधा कलाकारBat
कोAnimal
भी असंदिग्ध है, अब जबकि केवल एक ही मौजूद हैAnimal
उदाहरण जोBat
में परिवर्तित किया जा सकता है।
का एक उदाहरण साझा करने की क्षमता Animal
माता-पिता के बीच Mammal
और WingedAnimal
के बीच मेमोरी ऑफ़सेट रिकॉर्ड करके सक्षम किया गया है Mammal
या WingedAnimal
सदस्य और आधार के लोग Animal
व्युत्पन्न वर्ग के भीतर। हालाँकि यह ऑफ़सेट सामान्य स्थिति में केवल रनटाइम पर ही जाना जा सकता है Bat
बनना चाहिए (vpointer
, Mammal
, vpointer
, WingedAnimal
, Bat
, Animal
). दो व्यवहार्य संकेत हैं, एक प्रति वंशानुक्रम पदानुक्रम जो वस्तुतः विरासत में मिलता है Animal
. इस उदाहरण में, एक के लिए Mammal
और एक के लिए WingedAnimal
. वस्तु का आकार इसलिए दो बिंदुओं से बढ़ गया है, लेकिन अब केवल एक ही है Animal
और कोई अस्पष्टता नहीं। प्रकार की सभी वस्तुएं Bat
एक ही vpointers का प्रयोग करेंगे, लेकिन प्रत्येक Bat
ऑब्जेक्ट में अपना अनूठा होगा Animal
वस्तु। यदि कोई अन्य वर्ग से प्राप्त होता है Mammal
, जैसे कि Squirrel
, फिर vpointer में Mammal
का हिस्सा Squirrel
आम तौर पर vpointer से अलग होगा Mammal
का हिस्सा Bat
हालांकि वे वही हो सकते हैं यदि Squirrel
वर्ग के समान आकार का हो Bat
.
अनेक पूर्वजों का अतिरिक्त उदाहरण
यह उदाहरण एक ऐसे मामले को दिखाता है जहां बेस क्लास A
एक कंस्ट्रक्टर चर है msg
और एक अतिरिक्त पूर्वज E
पोते वर्ग से लिया गया है D
.
A / \ B C \ / D | E
यहाँ, A
दोनों में बनाया जाना चाहिए D
और E
. इसके अलावा, चर का निरीक्षण msg
दिखाता है कि कैसे वर्ग A
इसके व्युत्पन्न वर्ग का एक सीधा आधार वर्ग बन जाता है, जो किसी मध्यवर्ती व्युत्पन्न वर्ग के आधार वर्ग के विपरीत होता है A
और अंतिम व्युत्पन्न वर्ग। नीचे दिए गए कोड को अंतःक्रियात्मक रूप से खोजा जा सकता है यहां।
#include <string>
#include <iostream>
class A {
private:
std::string _msg;
public:
A(std::string x): _msg(x) {}
void test(){ std::cout<<"hello from A: "<<_msg <<"\n"; }
};
// B,C inherit A virtually
class B: virtual public A { public: B(std::string x):A("b"){} };
class C: virtual public A { public: C(std::string x):A("c"){} };
// Compile error when :A("c") is removed (since A's constructor is not called)
//class C: virtual public A { public: C(std::string x){} };
//class C: virtual public A { public: C(std::string x){ A("c"); } }; // Same compile error
// Since B, C inherit A virtually, A must be constructed in each child
class D: public B,C { public: D(std::string x):A("d_a"),B("d_b"),C("d_c"){} };
class E: public D { public: E(std::string x):A("e_a"),D("e_d"){} };
// Compile error without constructing A
//class D: public B,C { public: D(std::string x):B(x),C(x){} };
// Compile error without constructing A
//class E: public D { public: E(std::string x):D(x){} };
int main(int argc, char ** argv){
D d("d");
d.test(); // hello from A: d_a
E e("e");
e.test(); // hello from A: e_a
}
शुद्ध आभासी तरीके
मान लीजिए कि आधार वर्ग में एक शुद्ध आभासी विधि परिभाषित है। यदि एक व्युत्पन्न वर्ग वस्तुतः आधार वर्ग को प्राप्त करता है, तो उस व्युत्पन्न वर्ग में शुद्ध आभासी विधि को परिभाषित करने की आवश्यकता नहीं होती है। हालाँकि, यदि व्युत्पन्न वर्ग आधार वर्ग को वस्तुतः इनहेरिट नहीं करता है, तो सभी आभासी विधियों को परिभाषित किया जाना चाहिए। नीचे दिए गए कोड को अंतःक्रियात्मक रूप से एक्सप्लोर किया जा सकता है यहां।
#include <string>
#include <iostream>
class A {
protected:
std::string _msg;
public:
A(std::string x): _msg(x) {}
void test(){ std::cout<<"hello from A: "<<_msg <<"\n"; }
virtual void pure_virtual_test() = 0;
};
// since B,C inherit A virtually, the pure virtual method pure_virtual_test doesn't need to be defined
class B: virtual public A { public: B(std::string x):A("b"){} };
class C: virtual public A { public: C(std::string x):A("c"){} };
// since B,C inherit A virtually, A must be constructed in each child
// however, since D does not inherit B,C virtually, the pure virtual method in A *must be defined*
class D: public B,C {
public:
D(std::string x):A("d_a"),B("d_b"),C("d_c"){}
void pure_virtual_test() override { std::cout<<"pure virtual hello from: "<<_msg <<"\n"; }
};
// it is not necessary to redefine the pure virtual method after the parent defines it
class E: public D {
public:
E(std::string x):A("e_a"),D("e_d"){}
};
int main(int argc, char ** argv){
D d("d");
d.test(); // hello from A: d_a
d.pure_virtual_test(); // pure virtual hello from: d_a
E e("e");
e.test(); // hello from A: e_a
e.pure_virtual_test(); // pure virtual hello from: e_a
}
संदर्भ
- ↑ Milea, Andrei. "Solving the Diamond Problem with Virtual Inheritance". Cprogramming.com. Retrieved 2010-03-08.
One of the problems that arises due to multiple inheritance is the diamond problem. A classical illustration of this is given by Bjarne Stroustrup (the creator of C++) in the following example:
- ↑ McArdell, Ralph (2004-02-14). "C++/What is virtual inheritance?". All Experts. Archived from the original on 2010-01-10. Retrieved 2010-03-08.
This is something you find may be required if you are using multiple inheritance. In that case it is possible for a class to be derived from other classes which have the same base class. In such cases, without virtual inheritance, your objects will contain more than one subobject of the base type the base classes share. Whether this is what is the required effect depends on the circumstances. If it is not then you can use virtual inheritance by specifying virtual base classes for those base types for which a whole object should only contain one such base class subobject.