2015-10-08 9 views
7

मान लें कि हमारे पास मूल वर्ग और व्युत्पन्न कक्षा में एक से अधिक वर्चुअल फ़ंक्शन हैं। माता-पिता व्युत्पन्न कक्षा दोनों के लिए vtable में इन वर्चुअल फ़ंक्शंस के लिए बनाया गया एक vtable होगा।संकलक कैसे जानता है कि vtable में कौन सी प्रविष्टि वर्चुअल फ़ंक्शन से मेल खाती है?

संकलक कैसे पता चलेगा कि vtable में कौन सी प्रविष्टि वर्चुअल फ़ंक्शन से मेल खाती है?

उदाहरण:

class Animal{ 
public: 
void fakeMethod1(){} 
virtual void getWeight(){} 
void fakeMethod2(){} 
virtual void getHeight(){} 
virtual void getType(){} 
}; 

class Tiger:public Animal{ 
public: 
void fakeMethod3(){} 
virtual void getWeight(){} 
void fakeMethod4(){} 
virtual void getHeight(){} 
virtual void getType(){} 
}; 
main(){ 
Animal a* = new Tiger(); 
a->getHeight(); // A will now point to the base address of vtable Tiger 
//How will the compiler know which entry in the vtable corresponds to the function getHeight()? 
} 

मैं अपने अनुसंधान के क्षेत्र में सटीक विवरण नहीं मिला है -

https://stackoverflow.com/a/99341/437894 =

"इस तालिका में समारोह कॉल को हल करने के रूप में यह शामिल किया जाता है उस वर्ग के सभी आभासी कार्यों के पते। "

फ़ंक्शन कॉल को हल करने के लिए उपयोग की जाने वाली तालिका कैसी है?

https://stackoverflow.com/a/203136/437894 =

"रन टाइम पर तो, कोड सिर्फ वस्तु की vptr vtbl का पता लगाने का उपयोग करता है, और वहाँ से वास्तविक अधिरोहित समारोह का पता।"

मैं इसे समझने में सक्षम नहीं हूं। Vtable वर्चुअल फ़ंक्शन का पता वास्तविक ओवरराइड फ़ंक्शन का पता नहीं है।

+2

कुछ भी नहीं है जो vtbl लेआउट निर्धारित करता है। लेकिन संकलक के लिए लगातार क्रम में कक्षा में आभासी कार्यों की संख्या के लिए एक प्राकृतिक तरीका है। ये संख्या vtbl में सूचकांक के रूप में कार्य करती हैं, जो प्रभावी रूप से फ़ंक्शन पॉइंटर्स की एक सरणी है। – Gene

+0

कंपाइलर जानता है कि vtable में क्या है क्योंकि यह vtable बनाया गया है। अस्पष्ट जो आप वास्तव में यहां पूछ रहे हैं। – EJP

+0

@ जीन कष्टप्रद, एमएसवीसी भी एक साथ ओवरलोड लोड करता है भले ही उन्हें उस क्रम में घोषित न किया जाए। (ओह, और स्वाभाविक रूप से चीजें आभासी विरासत आदि के साथ अजीब हो जाती हैं) – Yakk

उत्तर

7

मैं आपके उदाहरण को थोड़ा संशोधित करूंगा ताकि यह ऑब्जेक्ट ओरिएंटेशन के अधिक दिलचस्प पहलू दिखाए।

मान लीजिए हम निम्नलिखित है:

#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_hellostatic घोषित किया गया। यह ऑनके है क्योंकि उन्हें कभी भी नाम से संदर्भित नहीं किया जाएगा, बल्कि केवल 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 के प्रकार का नाम बनाता है। (## पूर्वप्रक्रमक के टोकन चिपकाने ऑपरेटर है। उदाहरण के लिए, यदि STYPEanimal है, तो STYPE ## _vtable_typeanimal_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 के साथ खेल रहे थे।

+0

विस्तृत स्पष्टीकरण के लिए धन्यवाद। इससे मदद मिली। एक और उदाहरण भी मिला। यहां वे बेस क्लास दोनों से वर्चुअल फ़ंक्शन की सवारी करने पर दो बेस क्लास और व्युत्पन्न कक्षाओं के साथ एक उदाहरण के बारे में बात करते हैं। [कड़ी] (http: //www.openrce।संगठन/लेख/पूर्ण% 5 फ़्यूज़/23) – Ashwin

+0

अपनी अगली कक्षा के लिए, कृपया 'वर्चुअल' विरासत को कवर करें और इसे सी में अनुकरण कैसे करें :) +1 एक तरफ के रूप में, इस प्रणाली का लाभ यह है कि आप Vtable से तलाक ले सकते हैं डेटा (इसे संगत रूप से स्टोर न करें), जो कुछ चालों को अनुमति दे सकता है। ये तकनीकें सी ++ में उपयोगी होती हैं जब आपको उस विभाजन को करने की आवश्यकता होती है (कहें, यदि आप कहीं भी अपने डेटा को एक आंतरिक बफर में स्टोर करना चाहते हैं, लेकिन इसे पॉलिमॉर्फिक रूप से कार्य करना चाहते हैं: या, एक सरणी में पैक की गई हल्के हल्के ऑब्जेक्ट्स, रन-लम्बाई- एन्कोडेड vtables कहीं और, जिसे आप पाठ प्रसंस्करण में उपयोग कर सकते हैं) – Yakk

+0

उत्तर के लिए बहुत बहुत धन्यवाद। एक सवाल: कंपाइलर को पहले स्थान पर कैसे पता चलता है कि इसे वर्चुअल फ़ंक्शन कॉल करना है? अगर मेरे पास था: एक्स * जेड = नया जेड(); इस तरह एक्स के पास कोई वर्चुअल फ़ंक्शंस नहीं है, वाई एक्स से विरासत में है और इसमें वर्चुअल फ़ंक्शन है, और जेड वाई से विरासत में है। उपरोक्त "z" से सभी फ़ंक्शन कॉल एक vptr के माध्यम से होनी चाहिए, लेकिन प्रकार एक्स * है, तो कंपाइलर कैसा है पता है कि "z" पर फ़ंक्शंस करने के लिए कॉल Vptr से गुजरना चाहिए या नहीं? –

2

यह आपके जैसे कुछ को लागू करने में मदद कर सकता है।

struct Bob; 
struct Bob_vtable { 
    void(*print)(Bob const*self) = 0; 
    Bob_vtable(void(*p)(Bob const*)):print(p){} 
}; 
template<class T> 
Bob_vtable const* make_bob_vtable(void(*print)(Bob const*)) { 
    static Bob_vtable const table(+print); 
    return &table; 
} 
struct Bob { 
    Bob_vtable const* vtable; 
    void print() const { 
    vtable->print(this); 
    } 
    Bob():vtable(make_bob_vtable<Bob>([](Bob const*self){ 
    std::cout << "Bob\n"; 
    })) {} 
protected: 
    Bob(Bob_vtable const* t):vtable(t){} 
}; 
struct Alice:Bob { 
    int x = 0; 
    Alice():Bob(make_bob_vtable<Alice>([](Bob const*self){ 
    std::cout << "Alice " << static_cast<Alice const*>(self)->x << '\n'; 
    })) {} 
}; 

live example

यहां हमारे पास Bob में संग्रहीत एक स्पष्ट vtable है। यह कार्यों की एक तालिका को इंगित करता है। गैर वर्चुअल सदस्य फ़ंक्शन print इसे सही तरीके से गतिशील रूप से प्रेषित करने के लिए उपयोग करता है।

Bob का निर्माता और व्युत्पन्न वर्ग Alice तालिका में विभिन्न मानों के साथ vtable को एक अलग मान (इस मामले में एक स्थिर स्थानीय के रूप में बनाया गया) पर सेट करें।

उपयोग करने के लिए कौन सा पॉइंटर Bob::print का अर्थ है - यह तालिका में ऑफ़सेट जानता है।

यदि हम ऐलिस में एक और वर्चुअल फ़ंक्शन जोड़ते हैं, तो इसका मतलब यह है कि vtable पॉइंटर वास्तविकता में struct Alice_vtable:Bob_vtable पर इंगित करेगा। कास्टिक/पुनर्नवीनीकरण कास्टिंग हमें "वास्तविक" तालिका प्राप्त करेगा, और हम आसानी से अतिरिक्त फ़ंक्शन पॉइंटर्स तक पहुंच सकते हैं।

जब हम वर्चुअल विरासत के साथ-साथ वर्चुअल फ़ंक्शंस के बारे में बात करते हैं तो चीजें अजनबी हो जाती हैं। मैं यह वर्णन करने के लिए योग्य नहीं हूं कि यह कैसे काम करता है।

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

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