मैं आपके उदाहरण को थोड़ा संशोधित करूंगा ताकि यह ऑब्जेक्ट ओरिएंटेशन के अधिक दिलचस्प पहलू दिखाए।
मान लीजिए हम निम्नलिखित है:
#include <iostream>
struct Animal
{
int age;
Animal(int a) : age {a} {}
virtual int setAge(int);
virtual void sayHello() const;
};
int
Animal::setAge(int a)
{
int prev = this->age;
this->age = a;
return prev;
}
void
Animal::sayHello() const
{
std::cout << "Hello, I'm an " << this->age << " year old animal.\n";
}
struct Tiger : Animal
{
int stripes;
Tiger(int a, int s) : Animal {a}, stripes {s} {}
virtual void sayHello() const override;
virtual void doTigerishThing();
};
void
Tiger::sayHello() const
{
std::cout << "Hello, I'm a " << this->age << " year old tiger with "
<< this->stripes << " stripes.\n";
}
void
Tiger::doTigerishThing()
{
this->stripes += 1;
}
int
main()
{
Tiger * tp = new Tiger {7, 42};
Animal * ap = tp;
tp->sayHello(); // call overridden function via derived pointer
tp->doTigerishThing(); // call child function via derived pointer
tp->setAge(8); // call parent function via derived pointer
ap->sayHello(); // call overridden function via base pointer
}
मैं अच्छी सलाह है कि virtual
समारोह के सदस्यों के साथ कक्षाएं इस उदाहरण के प्रयोजन के लिए एक virtual
नाशक होना चाहिए अनदेखी कर रहा हूँ। मैं वैसे भी वस्तु को रिसाव करने जा रहा हूँ।
चलो देखते हैं कि हम इस उदाहरण का अनुवाद पुराने पुराने सी में कैसे कर सकते हैं जहां कोई सदस्य कार्य नहीं है, virtual
वाले के साथ अकेले रहें। निम्नलिखित सभी कोड सी है, सी ++ नहीं।
struct animal
सरल है:
struct animal
{
const void * vptr;
int age;
};
age
सदस्य के अलावा, हमारे पास एक vptr
कि vtable सूचक हो जाएगा जोड़ लिया है। मैं इसके लिए void
पॉइंटर का उपयोग कर रहा हूं क्योंकि हमें वैसे भी बदसूरत करना होगा और void *
का उपयोग करके कुरूपता को कम कर देगा।
अगला, हम सदस्य कार्यों को कार्यान्वित कर सकते हैं।
static int
animal_set_age(void * p, int a)
{
struct animal * this = (struct animal *) p;
int prev = this->age;
this->age = a;
return prev;
}
नोट अतिरिक्त 0-वें तर्क: this
सूचक है कि सी ++ में परोक्ष पारित कर दिया है। दोबारा, मैं void *
पॉइंटर का उपयोग कर रहा हूं क्योंकि यह बाद में चीजों को सरल बना देगा। ध्यान दें कि के अंदर कोई भी सदस्य फ़ंक्शन, हम हमेशा this
पॉइंटर के प्रकार को स्थिर रूप से जानते हैं ताकि कलाकार कोई समस्या न हो। (और मशीन के स्तर पर है, यह कुछ भी वैसे भी नहीं करता है।)
sayHello
सदस्य को छोड़कर this
सूचक const
इस समय योग्य है कि इसी तरह परिभाषित किया गया है।
static void
animal_say_hello(const void * p)
{
const struct animal * this = (const struct animal *) p;
printf("Hello, I'm an %d year old animal.\n", this->age);
}
पशु vtable के लिए समय। सबसे पहले हमें इसे एक प्रकार देना होगा, जो सीधे आगे है।
struct animal_vtable_type
{
int (*setAge)(void *, int);
void (*sayHello)(const void *);
};
फिर हम vtable का एक उदाहरण बनाते हैं और इसे सही सदस्य कार्यों के साथ सेट करते हैं। यदि Animal
में शुद्ध virtual
सदस्य था, तो इसी प्रविष्टि में NULL
मान होगा और बेहतर तरीके से संदर्भित नहीं किया गया था।
static const struct animal_vtable_type animal_vtable = {
.setAge = animal_set_age,
.sayHello = animal_say_hello,
};
ध्यान दें कि animal_set_age
और animal_say_hello
static
घोषित किया गया। यह ऑनके है क्योंकि उन्हें कभी भी नाम से संदर्भित नहीं किया जाएगा, बल्कि केवल vtable के माध्यम से (और केवल vptr
के माध्यम से vtable) तो यह static
भी हो सकता है)।
अब हम Animal
के लिए निर्माता को लागू कर सकते हैं ...
void
animal_ctor(void * p, int age)
{
struct animal * this = (struct animal *) p;
this->vptr = &animal_vtable;
this->age = age;
}
... और इसी operator new
:
void *
animal_new(int age)
{
void * p = malloc(sizeof(struct animal));
if (p != NULL)
animal_ctor(p, age);
return p;
}
केवल एक चीज दिलचस्प बारे लाइन जहां vptr
निर्माता में सेट कर दिया जाता है।
चलो बाघों पर चले जाते हैं। Animal
से
Tiger
inherits इसलिए यह एक struct tiger
उप वस्तु हो जाता है। मैं इसे पहले सदस्य के रूप में struct animal
रख कर ऐसा कर रहा हूं। यह आवश्यक है कि यह पहला सदस्य है क्योंकि इसका मतलब है कि उस ऑब्जेक्ट का पहला सदस्य - vptr
- हमारे ऑब्जेक्ट के समान पता है। हमें बाद में इसकी आवश्यकता होगी जब हम कुछ मुश्किल कास्टिंग करेंगे।
struct tiger
{
struct animal base;
int stripes;
};
हम भी बस lexically struct tiger
की परिभाषा की शुरुआत में struct animal
के सदस्यों की नकल की है सकता है, लेकिन है कि रेख करने में कठिन हो सकता है। एक कंपाइलर ऐसे स्टाइलिस्ट मुद्दों के बारे में परवाह नहीं करता है।
हम पहले से ही जानते हैं कि बाघों के लिए सदस्य कार्यों को कैसे कार्यान्वित किया जाए।
void
tiger_say_hello(const void * p)
{
const struct tiger * this = (const struct tiger *) p;
printf("Hello, I'm an %d year old tiger with %d stripes.\n",
this->base.age, this->stripes);
}
void
tiger_do_tigerish_thing(void * p)
{
struct tiger * this = (struct tiger *) p;
this->stripes += 1;
}
ध्यान दें कि हम struct tiger
इस समय के लिए this
सूचक कास्टिंग कर रहे हैं। यदि बाघ समारोह कहा जाता है, तो this
पॉइंटर के बाघ को बेहतर बिंदु था, भले ही हमें बेस पॉइंटर के माध्यम से बुलाया जाता है।
vtable के लिए अगला:
struct tiger_vtable_type
{
int (*setAge)(void *, int);
void (*sayHello)(const void *);
void (*doTigerishThing)(void *);
};
ध्यान दें कि पहले दो सदस्यों बिल्कुल animal_vtable_type
के लिए जैसे ही हैं। यह आवश्यक है और मूल रूप से आपके प्रश्न का सीधा जवाब है। यह अधिक स्पष्ट होता, शायद, अगर मैंने पहले सदस्य के रूप में struct animal_vtable_type
रखा था। मैं इस बात पर ज़ोर देना चाहता हूं कि ऑब्जेक्ट लेआउट ठीक उसी को छोड़कर होगा, सिवाय इसके कि हम इस मामले में हमारी बुरा कास्टिंग चाल नहीं खेल सके। फिर, ये सी भाषा के पहलू हैं, मशीन स्तर पर मौजूद नहीं हैं, इसलिए एक कंपाइलर इससे परेशान नहीं है।
एक vtable उदाहरण बनाएँ:
static const struct tiger_vtable_type tiger_vtable = {
.setAge = animal_set_age,
.sayHello = tiger_say_hello,
.doTigerishThing = tiger_do_tigerish_thing,
};
और निर्माता को लागू:
void
tiger_ctor(void * p, int age, int stripes)
{
struct tiger * this = (struct tiger *) p;
animal_ctor(this, age);
this->base.vptr = &tiger_vtable;
this->stripes = stripes;
}
पहली बात बाघ निर्माता करता पशु निर्माता बुला रहा है। याद रखें कि पशु कन्स्ट्रक्टर vptr
को &animal_vtable
पर कैसे सेट करता है? यही वजह है कि virtual
बेस क्लास कन्स्ट्रक्टर ऑफर आश्चर्यजनक लोगों के सदस्य फ़ंक्शन को कॉल करना। केवल बेस क्लास कन्स्ट्रक्टर चलाने के बाद, हम vptr
व्युत्पन्न प्रकार पर फिर से असाइन करते हैं और फिर अपना प्रारंभिक कार्य करते हैं।
operator new
बस बॉयलरप्लेट है।
void *
tiger_new(int age, int stripes)
{
void * p = malloc(sizeof(struct tiger));
if (p != NULL)
tiger_ctor(p, age, stripes);
return p;
}
हम कर रहे हैं। लेकिन हम वर्चुअल सदस्य फ़ंक्शन कैसे कॉल करते हैं? इसके लिए, मैं एक सहायक मैक्रो परिभाषित करूंगा।
#define INVOKE_VIRTUAL_ARGS(STYPE, THIS, FUNC, ...) \
(*((const struct STYPE ## _vtable_type * *) (THIS)))->FUNC(THIS, __VA_ARGS__)
अब, यह बदसूरत है। यह क्या करता है स्थिर प्रकार STYPE
, this
पॉइंटर THIS
और सदस्य फ़ंक्शन FUNC
का नाम और फ़ंक्शन को पास करने के लिए कोई अतिरिक्त तर्क ले रहा है।
फिर, यह स्थैतिक प्रकार से vtable के प्रकार का नाम बनाता है। (##
पूर्वप्रक्रमक के टोकन चिपकाने ऑपरेटर है। उदाहरण के लिए, यदि STYPE
animal
है, तो STYPE ## _vtable_type
animal_vtable_type
करने का विस्तार होगा।)
इसके बाद, THIS
सूचक सिर्फ व्युत्पन्न vtable प्रकार के सूचक के लिए एक सूचक को casted है। यह काम करता है क्योंकि हमने को प्रत्येक ऑब्जेक्ट में पहले सदस्य के रूप में रखना सुनिश्चित किया है, इसलिए यह वही पता है। यह आवश्यक है।
एक बार यह हो जाने के बाद, हम पॉइंटर को खराब कर सकते हैं (वास्तविक vptr
प्राप्त करने के लिए) और उसके बाद FUNC
सदस्य से पूछें और अंत में इसे कॉल करें। (__VA_ARGS__
अतिरिक्त विविध मैक्रो तर्कों का विस्तार करता है।) ध्यान दें कि हम THIS
पॉइंटर को सदस्य फ़ंक्शन के 0-वें तर्क के रूप में भी पास करते हैं।
अब, वास्तविक सत्य यह है कि मुझे उन कार्यों के लिए लगभग समान मैक्रो को फिर से परिभाषित करना पड़ा जो कोई तर्क नहीं लेते क्योंकि प्रीप्रोसेसर एक भिन्न मैक्रो तर्क पैक खाली होने की अनुमति नहीं देता है। ये ऐसा होना चाहिए।
#define INVOKE_VIRTUAL(STYPE, THIS, FUNC) \
(*((const struct STYPE ## _vtable_type * *) (THIS)))->FUNC(THIS)
और यह काम करता है:
#include <stdio.h>
#include <stdlib.h>
/* Insert all the code from above here... */
int
main()
{
struct tiger * tp = tiger_new(7, 42);
struct animal * ap = (struct animal *) tp;
INVOKE_VIRTUAL(tiger, tp, sayHello);
INVOKE_VIRTUAL(tiger, tp, doTigerishThing);
INVOKE_VIRTUAL_ARGS(tiger, tp, setAge, 8);
INVOKE_VIRTUAL(animal, ap, sayHello);
return 0;
}
आप सोच रहे होंगे कि क्या
INVOKE_VIRTUAL_ARGS(tiger, tp, setAge, 8);
कॉल में होता है। हम ऑब्जेक्ट पर struct tiger
पॉइंटर के माध्यम से संदर्भित Animal
के गैर-ओवरराइड setAge
सदस्य को आमंत्रित करने के लिए क्या कर रहे हैं। यह सूचक पहली बार void
पॉइंटर पर लगाया गया है और इस तरह this
पॉइंटर animal_set_age
के रूप में पारित किया गया है। उस समारोह में इसे struct animal
पॉइंटर पर रखा जाता है। क्या ये सही है? ऐसा इसलिए है, क्योंकि हम struct animal
को struct tiger
में पहले सदस्य के रूप में रखने के लिए सावधान थे, इसलिए struct tiger
ऑब्जेक्ट का पता struct animal
उप-ऑब्जेक्ट के पते के समान है। यह एक ही चाल है (केवल एक स्तर कम) हम vptr
के साथ खेल रहे थे।
कुछ भी नहीं है जो vtbl लेआउट निर्धारित करता है। लेकिन संकलक के लिए लगातार क्रम में कक्षा में आभासी कार्यों की संख्या के लिए एक प्राकृतिक तरीका है। ये संख्या vtbl में सूचकांक के रूप में कार्य करती हैं, जो प्रभावी रूप से फ़ंक्शन पॉइंटर्स की एक सरणी है। – Gene
कंपाइलर जानता है कि vtable में क्या है क्योंकि यह vtable बनाया गया है। अस्पष्ट जो आप वास्तव में यहां पूछ रहे हैं। – EJP
@ जीन कष्टप्रद, एमएसवीसी भी एक साथ ओवरलोड लोड करता है भले ही उन्हें उस क्रम में घोषित न किया जाए। (ओह, और स्वाभाविक रूप से चीजें आभासी विरासत आदि के साथ अजीब हो जाती हैं) – Yakk