2013-04-04 24 views
16

सबसे पहले, मैं समझता हूं कि क्यों virtual एकल विरासत के मामले में विनाशकों की आवश्यकता होती है और आधार सूचक के माध्यम से किसी ऑब्जेक्ट को हटाया जाता है। यह विशेष रूप से एकाधिक विरासत और के पीछे कारण है यह काम करता है। यह सवाल मेरे विश्वविद्यालय वर्गों में से एक में आया था, और कोई भी (प्रोफेसर सहित) यकीन है कि क्यों यह काम किया था:आभासी विनाशक और एकाधिक विरासत वाले वस्तुओं को हटाने ... यह कैसे काम करता है?

#include <iostream> 

struct A 
{ 
    virtual ~A() 
    { 
     std::cout << "~A" << std::endl; 
    } 
    int memberA; 
}; 

struct B 
{ 
    virtual ~B() 
    { 
     std::cout << "~B" << std::endl; 
    } 
    int memberB; 
}; 

struct AB : public A, public B 
{ 
    virtual ~AB() 
    { 
     std::cout << "~AB" << std::endl; 
    } 
}; 

int main() 
{ 
    AB* ab1 = new AB(); 
    AB* ab2 = new AB(); 

    A* a = ab1; 
    B* b = ab2; 

    delete a; 
    delete b; 
} 

इस के लिए उत्पादन होता है:

~AB
~B
~A
~AB
~B
~A

संकलक कैसे जान लेता है कि कैसे A की और B 'कहते हैं जब a या b को हटाने रों नाशक के लिए? विशेष रूप से, AB के लिए मेमोरी कैसा है (विशेष रूप से यह वर्चुअल फ़ंक्शन टेबल), जैसे कि A और B विध्वंसक कहलाए जा सकते हैं?

मेरे प्रोफेसर थी कि मेमोरी का सुझाव दे (कुछ) इस तरह बाहर रखी जाएगी:

AB 
+---------+    +----+ 
| A VFT | - - - - - -> | ~A | 
+---------+    +----+ 
| memberA | 
+---------+    +----+ 
| B VFT | - - - - - -> | ~B | 
+---------+    +----+ 
| memberB | 
+---------+ 

// I have no idea where ~AB would go... 

हम सभी उत्सुक कैसे इन विनाशकर्ता वास्तव में स्मृति में बाहर रखी और कर रहे हैं कर रहे हैं कि कैसे या तो a या b पर delete बुला परिणामस्वरूप सभी विनाशकों को सही ढंग से बुलाया जा रहा है। यह समझ में आता है कि बेस ऑब्जेक्ट को एकल विरासत में काम करना (क्योंकि काम करने के लिए एक वर्चुअल फ़ंक्शन टेबल है), लेकिन स्पष्ट रूप से मैं चीजों को सही ढंग से समझ नहीं रहा क्योंकि मैं एकल विरासत संस्करण की समझ नहीं ले सकता और इसे लागू नहीं कर सकता इस एकाधिक विरासत उदाहरण के लिए।

तो यह कैसे काम करता है?

+1

ठीक है, vtable प्रविष्टियों को '~ एबी ',' ए 'या' ~ बी' पर इंगित करने की आवश्यकता होगी ... –

+0

@OliCharlesworth: आह, यह निश्चित रूप से अधिक समझ में आ जाएगा। – Cornstalks

उत्तर

14

यह काम करता है क्योंकि मानक कहता है कि यह काम करता है।

प्रैक्टिस में, कंपाइलर ~A() और ~B() को ~AB() पर अंतर्निहित कॉल सम्मिलित करता है। यह तंत्र बिल्कुल एक ही विरासत जैसा ही है, सिवाय इसके कि कंपाइलर को कॉल करने के लिए कई आधार विनाशक हैं।

मुझे लगता है कि आपके आरेख में भ्रम का मुख्य स्रोत वर्चुअल विनाशक के लिए कई अलग-अलग vtable प्रविष्टियां हैं। व्यावहारिक रूप से, एक एकल प्रविष्टि होगी जो ~A(), ~B() और ~AB()A, B और AB() के लिए क्रमशः इंगित करेगी।

उदाहरण के लिए, अगर मैं gcc का उपयोग कर अपने कोड संकलन और विधानसभा की जांच, मैं निम्नलिखित कोड ~AB() में देखें:

LEHE0: 
     movq -24(%rbp), %rax 
     addq $16, %rax 
     movq %rax, %rdi 
LEHB1: 
     call __ZN1BD2Ev 
LEHE1: 
     movq -24(%rbp), %rax 
     movq %rax, %rdi 
LEHB2: 
     call __ZN1AD2Ev 

यह ~B()~A() के बाद कॉल।

; A 
__ZTV1A: 
     .quad 0 
     .quad __ZTI1A 
     .quad __ZN1AD1Ev 
     .quad __ZN1AD0Ev 

; B 
__ZTV1B: 
     .quad 0 
     .quad __ZTI1B 
     .quad __ZN1BD1Ev 
     .quad __ZN1BD0Ev 

; AB 
__ZTV2AB: 
     .quad 0 
     .quad __ZTI2AB 
     .quad __ZN2ABD1Ev 
     .quad __ZN2ABD0Ev 
     .quad -16 
     .quad __ZTI2AB 
     .quad __ZThn16_N2ABD1Ev 
     .quad __ZThn16_N2ABD0Ev 

प्रत्येक वर्ग के लिए, प्रविष्टि # 2 वर्ग के "पूरा वस्तु नाशक" को संदर्भित करता है:

तीन वर्गों की आभासी टेबल इस प्रकार है। A के लिए, यह ~A() आदि

11

vtable प्रविष्टि बस AB के लिए विनाशक पर इंगित करता है। यह सिर्फ परिभाषित किया गया है कि एक नाशक के निष्पादन के बाद, आधार वर्ग विनाशकर्ता तो कहा जाता है:

नाशक के शरीर को क्रियान्वित करने और शरीर के भीतर आवंटित किसी भी स्वत: वस्तुओं को नष्ट करने के बाद, दसवीं कक्षा के लिए एक नाशक कॉल [। ..] X के प्रत्यक्ष आधार वर्गों के लिए विनाशक और [...]।

तो जब संकलक delete a; देखता है और फिर देखता है कि A का नाशक आभासी है, यह a के गतिशील प्रकार (जो AB है) के लिए नाशक अप vtable का उपयोग करके लग रहा है। यह ~AB पाता है और इसे निष्पादित करता है। इसके परिणामस्वरूप ~A और ~B पर कॉल किया गया।

यह vtable नहीं है जो कहता है "कॉल ~AB, फिर ~A, फिर ~B"; यह बस कहता है "कॉल ~AB" जो ~A और ~B पर कॉल करता है।

1

विनाशकों को "सबसे अधिक बेसल से अधिक व्युत्पन्न" कहा जाता है, और घोषणा के विपरीत क्रम में। तो ~AB को पहले कहा जाता है, फिर ~B, फिर ~A, क्योंकि AB सबसे व्युत्पन्न कक्षा है।

स्मृति को वास्तव में मुक्त करने से पहले सभी विनाशकों को बुलाया जाता है। वास्तव में वर्चुअल फ़ंक्शन पॉइंटर्स कैसे संग्रहीत किए जाते हैं, एक कार्यान्वयन विवरण है, और वास्तव में कुछ ऐसा है जिसके बारे में आपको चिंतित नहीं होना चाहिए। एकाधिक विरासत वाले वर्ग में संभवतः कक्षाओं के VTABLES में दो पॉइंटर्स होंगे, जो तब तक प्राप्त होते हैं, लेकिन जब तक संकलक और रनटाइम लाइब्रेरी "हम अपेक्षा करते हैं तो काम करते हैं", यह पूरी तरह से कंपाइलर + रनटाइम लाइब्रेरी तक है इस तरह के मुद्दों को हल करने के लिए वे क्या चाहते हैं।

+0

"वास्तव में आपको कुछ चिंतित नहीं होना चाहिए" अरे, चलो, यह जानना दिलचस्प है! :) (भले ही यह कार्यान्वयन से कार्यान्वयन में भिन्न हो) – Cornstalks

0

(मैं जानता हूँ कि इस सवाल का लगभग दो साल पुराना है, लेकिन मैं एक बिंदु बनाने का विरोध नहीं कर सकता है के बाद मैं इसे भर में आया था)

हालांकि शीर्षक में आपको प्रश्न शब्द का उपयोग कैसे, आप भी उल्लेख प्रश्न पोस्ट में क्यों। लोगों ने इस बात के लिए अच्छे तकनीकी उत्तर दिए हैं कि क्यों ऐसा लगता है कि क्यों नाराज हो गया है।

यही कारण है कि इस काम करता है

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

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