आभासी विधि तालिका
कंप्यूटर प्रोग्रामिंग में, एक वर्चुअल मेथड टेबल (VMT), आभासी समारोह टेबल, वर्चुअल कॉल टेबल, प्रेषण तालिका , vtable, या vftable एक ऐसी प्रणाली है जिसका [[डी (प्रोग्रामिंग भाषा)]] में गतिशील प्रेषण (या रन टाइम (प्रोग्राम जीवनचक्र चरण)) चलाने के लिए किया जाता है। -टाइम मेथड नाम बंधन )।
जब भी कोई वर्ग (कंप्यूटर प्रोग्रामिंग) एक वर्चुअल फ़ंक्शन (या विधि (कंप्यूटर प्रोग्रामिंग) ) को परिभाषित करता है, तो अधिकांश संकलक क्लास में एक छिपे हुए सदस्य चर को जोड़ते हैं जो सूचक (कंप्यूटर प्रोग्रामिंग) की एक सरणी को (वर्चुअल) फ़ंक्शन को इंगित करता है जिसे वर्चुअल कहा जाता है। विधि तालिका। इन पॉइंटर्स का उपयोग रनटाइम पर उपयुक्त फ़ंक्शन कार्यान्वयनों को लागू करने के लिए किया जाता है, क्योंकि संकलन समय पर यह अभी तक ज्ञात नहीं हो सकता है कि बेस फ़ंक्शन को कॉल किया जाना है या किसी वर्ग द्वारा लागू किया गया एक व्युत्पन्न है जो आधार से वंशानुक्रम (ऑब्जेक्ट-ओरिएंटेड प्रोग्रामिंग) है। कक्षा।
ऐसे डायनेमिक डिस्पैच को लागू करने के कई अलग-अलग तरीके हैं, लेकिन वर्चुअल मेथड टेबल का उपयोग विशेष रूप से C++ और संबंधित भाषाओं (जैसे D (प्रोग्रामिंग लैंग्वेज) और C Sharp (प्रोग्रामिंग लैंग्वेज)|C#) के बीच आम है। मूल दृश्य और डेल्फी (प्रोग्रामिंग भाषा) जैसी भाषाएं जो कार्यान्वयन से वस्तुओं के प्रोग्रामेटिक इंटरफ़ेस को अलग करती हैं, वे भी इस दृष्टिकोण का उपयोग करती हैं, क्योंकि यह ऑब्जेक्ट को विधि पॉइंटर्स के एक अलग सेट का उपयोग करके एक अलग कार्यान्वयन का उपयोग करने की अनुमति देता है।
मान लीजिए कि एक कार्यक्रम में वंशानुक्रम पदानुक्रम में तीन वर्ग हैं: एक सुपरक्लास (कंप्यूटर विज्ञान), Cat, और दो उपवर्ग (कंप्यूटर विज्ञान), HouseCat और Lion. कक्षा Cat नाम के एक वर्चुअल फंक्शन को परिभाषित करता है speak, इसलिए इसके उपवर्ग उचित कार्यान्वयन प्रदान कर सकते हैं (उदाहरण के लिए या तो meow या roar). जब कार्यक्रम कॉल करता है speak ए पर कार्य करता है Cat संदर्भ (जो एक उदाहरण को संदर्भित कर सकता है Cat, या का एक उदाहरण HouseCat या Lion), कोड को यह निर्धारित करने में सक्षम होना चाहिए कि फ़ंक्शन के किस कार्यान्वयन को कॉल भेजा जाना चाहिए। यह वस्तु के वास्तविक वर्ग पर निर्भर करता है, न कि इसके संदर्भ के वर्ग पर (Cat). वर्ग को आम तौर पर स्थिर रूप से निर्धारित नहीं किया जा सकता है (अर्थात संकलन समय पर), इसलिए न तो संकलक यह तय कर सकता है कि उस समय कौन सा फ़ंक्शन कॉल करना है। कॉल को गतिशील रूप से (यानी, रन टाइम (प्रोग्राम लाइफसाइकिल चरण) पर) सही फ़ंक्शन पर भेजा जाना चाहिए।
कार्यान्वयन
ऑब्जेक्ट की वर्चुअल विधि तालिका में ऑब्जेक्ट की गतिशील रूप से बाध्य विधियों का मेमोरी पता होगा। ऑब्जेक्ट के वर्चुअल मेथड टेबल से मेथड का पता लाकर मेथड कॉल की जाती है। वर्चुअल विधि तालिका एक ही वर्ग से संबंधित सभी वस्तुओं के लिए समान होती है, और इसलिए आमतौर पर उनके बीच साझा की जाती है। प्रकार-संगत वर्गों से संबंधित वस्तुएं (उदाहरण के लिए वंशानुक्रम पदानुक्रम में भाई-बहन) एक ही लेआउट के साथ आभासी विधि तालिकाएँ होंगी: किसी दिए गए विधि का पता सभी प्रकार-संगत वर्गों के लिए समान ऑफ़सेट पर दिखाई देगा। इस प्रकार, किसी दिए गए ऑफ़सेट से वर्चुअल विधि तालिका में विधि का पता लाने से ऑब्जेक्ट की वास्तविक कक्षा से संबंधित विधि मिल जाएगी।[1] सी ++ मानकों को अनिवार्य नहीं है कि गतिशील प्रेषण को कैसे कार्यान्वित किया जाना चाहिए, लेकिन कंपाइलर्स आम तौर पर एक ही मूल मॉडल पर मामूली बदलाव का उपयोग करते हैं।
आमतौर पर, कंपाइलर प्रत्येक वर्ग के लिए एक अलग वर्चुअल मेथड टेबल बनाता है। जब कोई ऑब्जेक्ट बनाया जाता है, तो इस तालिका में एक पॉइंटर, जिसे वर्चुअल टेबल पॉइंटर, vpointer या VPTR कहा जाता है, इस ऑब्जेक्ट के छिपे हुए सदस्य के रूप में जोड़ा जाता है। जैसे, कंपाइलर को प्रत्येक वर्ग के कंस्ट्रक्टर (ऑब्जेक्ट-ओरिएंटेड प्रोग्रामिंग) में छिपे हुए कोड को भी उत्पन्न करना चाहिए ताकि एक नए ऑब्जेक्ट के वर्चुअल टेबल पॉइंटर को उसके क्लास के वर्चुअल मेथड टेबल के पते पर इनिशियलाइज़ किया जा सके।
कई कंपाइलर वर्चुअल टेबल पॉइंटर को ऑब्जेक्ट के अंतिम सदस्य के रूप में रखते हैं; अन्य संकलक इसे पहले स्थान पर रखते हैं; पोर्टेबल स्रोत कोड किसी भी तरह से काम करता है।[2] उदाहरण के लिए, g++ ने पहले पॉइंटर को ऑब्जेक्ट के अंत में रखा था।[3]
उदाहरण
सी ++ सिंटैक्स में निम्नलिखित वर्ग घोषणाओं पर विचार करें:
class B1 {
public:
virtual ~B1() {}
void fnonvirtual() {}
virtual void f1() {}
int int_in_b1;
};
class B2 {
public:
virtual ~B2() {}
virtual void f2() {}
int int_in_b2;
};
निम्नलिखित वर्ग प्राप्त करने के लिए प्रयोग किया जाता है:
class D : public B1, public B2 {
public:
void d() {}
void f2() override {}
int int_in_d;
};
और सी ++ कोड का निम्न भाग:
B2 *b2 = new B2();
D *d = new D();
जीएनयू कंपाइलर संग्रह से जी ++ 3.4.6 वस्तु के लिए निम्नलिखित 32-बिट मेमोरी लेआउट उत्पन्न करता है b2
:[nb 1]
<पूर्व>
बी 2:
+0: बी 2 की वर्चुअल विधि तालिका के सूचक +4: int_in_b2 का मान
B2 की आभासी विधि तालिका:
+0: बी 2 :: एफ 2 ()
</पूर्व>
और ऑब्जेक्ट के लिए निम्न मेमोरी लेआउट d
:
<पूर्व>
डी:
+0: डी की वर्चुअल विधि तालिका के लिए सूचक (बी 1 के लिए) +4: int_in_b1 का मान +8: डी की आभासी विधि तालिका के सूचक (बी 2 के लिए) +12: int_in_b2 का मान +16: int_in_d का मान
कुल आकार: 20 बाइट्स।
D की आभासी विधि तालिका (B1 के लिए):
+0: बी 1 :: एफ 1 () // बी 1 :: एफ 1 () ओवरराइड नहीं है
D की आभासी विधि तालिका (B2 के लिए):
+0: डी :: एफ 2 () // बी 2 :: एफ 2 () डी :: एफ 2 () द्वारा ओवरराइड किया गया है // बी 2 :: एफ 2 का स्थान डी के लिए वर्चुअल विधि तालिका में नहीं है
</पूर्व>
ध्यान दें कि वे कार्य कीवर्ड नहीं ले रहे हैं virtual
उनकी घोषणा में (जैसे fnonvirtual()
और d()
) आमतौर पर वर्चुअल मेथड टेबल में दिखाई नहीं देते हैं। डिफ़ॉल्ट कंस्ट्रक्टर द्वारा उत्पन्न विशेष मामलों के अपवाद हैं।
वर्चुअल फ़ंक्शन # बेस क्लास में वर्चुअल डिस्ट्रक्टर्स पर भी ध्यान दें, B1
और B2
. उन्हें सुनिश्चित करना आवश्यक है delete d
न केवल स्मृति को मुक्त कर सकते हैं D
, बल्कि इसके लिए भी B1
और B2
, अगर d
एक सूचक या प्रकार का संदर्भ है B1
या B2
. उदाहरण को सरल रखने के लिए उन्हें मेमोरी लेआउट से बाहर रखा गया था। [nb 2]
ओवरराइडिंग विधि f2()
कक्षा में D
की वर्चुअल विधि तालिका को डुप्लिकेट करके कार्यान्वित किया जाता है B2
और सूचक की जगह B2::f2()
एक सूचक के साथ D::f2()
.
एकाधिक वंशानुक्रम और थंक्स
जी ++ कंपाइलर कक्षाओं की एकाधिक विरासत को लागू करता है B1
और B2
कक्षा में D
दो वर्चुअल मेथड टेबल का उपयोग करना, प्रत्येक बेस क्लास के लिए एक। (मल्टीपल इनहेरिटेंस को लागू करने के अन्य तरीके हैं, लेकिन यह सबसे आम है।) यह पॉइंटर फिक्सअप की आवश्यकता की ओर ले जाता है, जिसे थंक (प्रोग्रामिंग) भी कहा जाता है, जब टाइप रूपांतरण होता है।
निम्नलिखित सी ++ कोड पर विचार करें:
D *d = new D();
B1 *b1 = d;
B2 *b2 = d;
जबकि d
और b1
इस कोड के निष्पादन के बाद उसी स्मृति स्थान को इंगित करेगा, b2
स्थान की ओर संकेत करेगा d+8
(आठ बाइट्स की स्मृति स्थान से परे d
). इस प्रकार, b2
भीतर के क्षेत्र की ओर इशारा करता है d
का एक उदाहरण लगता है B2
, यानी, एक उदाहरण के रूप में एक ही मेमोरी लेआउट है B2
.
मंगलाचरण
को एक कॉल d->f1()
dereferencing द्वारा नियंत्रित किया जाता है d
'एस D::B1
vpointer, ऊपर देख रहा है f1
वर्चुअल मेथड टेबल में एंट्री, और फिर कोड को कॉल करने के लिए उस पॉइंटर को डिफ्रेंस करना।
एकल वंशानुक्रम
एकल वंशानुक्रम (या केवल एकल वंशानुक्रम वाली भाषा में) के मामले में, यदि vpointer हमेशा पहला तत्व होता है d
(जैसा कि यह कई कंपाइलर्स के साथ है), यह निम्न छद्म-सी ++ को कम करता है:
(*((*d)[0]))(d)
कहाँ *d
की आभासी विधि तालिका को संदर्भित करता है D
और [0]
वर्चुअल विधि तालिका में पहली विधि को संदर्भित करता है। पैरामीटर d
यह (कंप्यूटर विज्ञान) बन जाता है |this
वस्तु के लिए सूचक।
एकाधिक वंशानुक्रम
अधिक सामान्य मामले में, कॉलिंग B1::f1()
या D::f2()
अधिक जटिल है:
(*(*(d[+0]/*pointer to virtual method table of D (for B1)*/)[0]))(d) /* Call d->f1() */
(*(*(d[+8]/*pointer to virtual method table of D (for B2)*/)[0]))(d+8) /* Call d->f2() */
को कॉल d->f1()
ए पास करता है B1
एक पैरामीटर के रूप में सूचक। को कॉल d->f2()
ए पास करता है B2
एक पैरामीटर के रूप में सूचक। इस दूसरी कॉल के लिए सही पॉइंटर बनाने के लिए फिक्सअप की आवश्यकता होती है। के स्थान B2::f2
के लिए वर्चुअल मेथड टेबल में नहीं है D
.
तुलना करके, एक कॉल करने के लिए d->fnonvirtual()
ज्यादा सरल है:
(*B1::fnonvirtual)(d)
दक्षता
एक गैर-वर्चुअल कॉल की तुलना में एक वर्चुअल कॉल के लिए कम से कम एक अतिरिक्त अनुक्रमित डीरेफरेंस और कभी-कभी एक फिक्सअप जोड़ की आवश्यकता होती है, जो केवल संकलित-इन पॉइंटर के लिए एक छलांग है। इसलिए, वर्चुअल फ़ंक्शंस को कॉल करना गैर-वर्चुअल फ़ंक्शंस को कॉल करने की तुलना में स्वाभाविक रूप से धीमा है। 1996 में किए गए एक प्रयोग से संकेत मिलता है कि निष्पादन समय का लगभग 6-13% केवल सही कार्य को भेजने में व्यतीत होता है, हालांकि ओवरहेड 50% जितना अधिक हो सकता है।[4] आधुनिक कार्यों पर आभासी कार्यों की लागत इतनी अधिक नहीं हो सकती है CPU बहुत बड़े कैश और बेहतर शाखा भविष्यवक्ता के कारण आर्किटेक्चर।
इसके अलावा, ऐसे वातावरण में जहां समय-समय पर संकलन उपयोग में नहीं है, वर्चुअल फ़ंक्शन कॉल आमतौर पर इनलाइन विस्तार नहीं हो सकते हैं। कुछ मामलों में संकलक के लिए यह संभव हो सकता है कि वह डिवर्चुअलाइज़ेशन के रूप में जानी जाने वाली प्रक्रिया को अंजाम दे, जिसमें उदाहरण के लिए, लुकअप और अप्रत्यक्ष कॉल को प्रत्येक इनलाइन बॉडी के सशर्त निष्पादन के साथ बदल दिया जाता है, लेकिन ऐसे अनुकूलन सामान्य नहीं हैं।
इस ओवरहेड से बचने के लिए, जब भी संकलन समय पर कॉल को हल किया जा सकता है, तो कंपाइलर आमतौर पर वर्चुअल मेथड टेबल का उपयोग करने से बचते हैं।
इस प्रकार, को कॉल करें f1
उपरोक्त को टेबल लुकअप की आवश्यकता नहीं हो सकती है क्योंकि संकलक यह बताने में सक्षम हो सकता है d
ए ही रख सकता है D
इस बिंदु पर, और D
ओवरराइड नहीं करता f1
. या संकलक (या अनुकूलक) यह पता लगाने में सक्षम हो सकता है कि इसका कोई उपवर्ग नहीं है B1
ओवरराइड करने वाले प्रोग्राम में कहीं भी f1
. को कॉल B1::f1
या B2::f2
शायद टेबल लुकअप की आवश्यकता नहीं होगी क्योंकि कार्यान्वयन स्पष्ट रूप से निर्दिष्ट है (हालांकि इसे अभी भी 'इस'-पॉइंटर फिक्सअप की आवश्यकता है)।
विकल्पों के साथ तुलना
डायनेमिक डिस्पैच प्राप्त करने के लिए वर्चुअल मेथड टेबल आमतौर पर एक अच्छा प्रदर्शन ट्रेड-ऑफ है, लेकिन उच्च प्रदर्शन के साथ बाइनरी ट्री डिस्पैच जैसे विकल्प हैं, लेकिन अलग-अलग लागतें हैं।[5] हालाँकि, वर्चुअल मेथड टेबल केवल विशेष पैरामीटर पर एकल प्रेषण की अनुमति देते हैं, एकाधिक प्रेषण (कॉमन लिस्प ऑब्जेक्ट सिस्टम, डायलन (प्रोग्रामिंग भाषा) , या जूलिया (प्रोग्रामिंग भाषा) ) के विपरीत, जहां सभी प्रकार के पैरामीटर हो सकते हैं भेजने में ध्यान में रखा जाना चाहिए।
वर्चुअल मेथड टेबल भी तभी काम करते हैं जब डिस्पैचिंग विधियों के ज्ञात सेट तक सीमित हो, इसलिए उन्हें बतख टाइपिंग लैंग्वेज (जैसे स्मॉलटाक, पायथन (प्रोग्रामिंग लैंग्वेज) या जावास्क्रिप्ट) के विपरीत संकलन समय पर निर्मित एक साधारण सरणी में रखा जा सकता है। .
ऐसी भाषाएँ जो इनमें से कोई एक या दोनों सुविधाएँ प्रदान करती हैं, अक्सर हैश तालिका में एक स्ट्रिंग या किसी अन्य समकक्ष विधि को देखकर प्रेषित होती हैं। इसे तेज़ बनाने के लिए कई तरह की तकनीकें हैं (उदाहरण के लिए, स्ट्रिंग इंटर्निंग/टोकनिंग विधि नाम, कैशिंग लुकअप, समय-समय पर संकलन)।
यह भी देखें
- आभासी कार्य
- आभासी विरासत
- शाखा तालिका
टिप्पणियाँ
- ↑ G++'s
-fdump-class-hierarchy
(starting with version 8:-fdump-lang-class
) argument can be used to dump virtual method tables for manual inspection. For AIX VisualAge XlC compiler, use-qdump_class_hierarchy
to dump class hierarchy and virtual function table layout. - ↑ "C++ - why there are two virtual destructor in the virtual table and where is address of the non-virtual function (gcc4.6.3)".
संदर्भ
- Margaret A. Ellis and Bjarne Stroustrup (1990) The Annotated C++ Reference Manual. Reading, MA: Addison-Wesley. (ISBN 0-201-51459-1)
- ↑ Ellis & Stroustrup 1990, pp. 227–232
- ↑ Danny Kalev. "C++ Reference Guide: The Object Model II". 2003. Heading "Inheritance and Polymorphism" and "Multiple Inheritance".
- ↑ "सी ++ एबीआई बंद मुद्दे". Archived from the original on 25 July 2011. Retrieved 17 June 2011.
{{cite web}}
: CS1 maint: bot: original URL status unknown (link) - ↑ Driesen, Karel and Hölzle, Urs, "The Direct Cost of Virtual Function Calls in C++", OOPSLA 1996
- ↑ Zendra, Olivier and Driesen, Karel, "Stress-testing Control Structures for Dynamic Dispatch in Java", pp. 105–118, Proceedings of the USENIX 2nd Java Virtual Machine Research and Technology Symposium, 2002 (JVM '02)