आभासी विधि तालिका

From Vigyanwiki

कंप्यूटर प्रोग्रामिंग में, एक वर्चुअल मेथड टेबल (वीएमटी), वर्चुअल फलन टेबल, वर्चुअल कॉल टेबल,प्रेषण तालिका , वीटेबल, या वीएफटेबल एक ऐसी प्रणाली है जिसका [[डी (प्रोग्रामिंग भाषा)]] में गतिशील प्रेषण (या रन टाइम (प्रोग्राम जीवनचक्र चरण)) चलाने के लिए किया जाता है।

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

ऐसे डायनेमिक डिस्पैच को लागू करने के कई अलग-अलग तरीके हैं, लेकिन वर्चुअल मेथड टेबल का उपयोग विशेष रूप से C++ और संबंधित भाषाओं (जैसे D (प्रोग्रामिंग लैंग्वेज) और C शार्प (प्रोग्रामिंग भाषा)) के बीच सामान्य है। विसुअल बेसिक और डेल्फी (प्रोग्रामिंग भाषा) जैसी भाषाएं जो कार्यान्वयन से वस्तुओं के प्रोग्रामेटिक अंतरापृष्ठ को अलग करती हैं, वे भी इस दृष्टिकोण का उपयोग करती हैं, क्योंकि यह ऑब्जेक्ट को विधि पॉइंटर्स के एक अलग सम्मुच्चय का उपयोग करके एक अलग कार्यान्वयन का उपयोग करने की अनुमति देता है।

मान लीजिए कि एक कार्यक्रम में वंशानुक्रम पदानुक्रम में तीन वर्ग हैं: एक सुपरक्लास (कंप्यूटर विज्ञान), Cat, और दो उपवर्ग (कंप्यूटर विज्ञान), HouseCat और Lion। वर्ग Cat नाम के एक वर्चुअल फंक्शन को speak परिभाषित करता है, इसलिए इसके उपवर्ग उचित कार्यान्वयन प्रदान कर सकते हैं (उदाहरण के लिए या तो meow या roar)। जब प्रोग्राम Cat संदर्भ पर speak फलन को कॉल करता है (जो Cat के उदाहरण, या HouseCat या Lion के उदाहरण को संदर्भित कर सकता है), तो कोड यह निर्धारित करने में सक्षम होना चाहिए कि कॉल को फलन के किस कार्यान्वयन के लिए भेजा जाना चाहिए। यह वस्तु के वास्तविक वर्ग पर निर्भर करता है, न कि (Cat) इसके संदर्भ के वर्ग पर निर्भर करता है। वर्ग को सामान्यतः स्थिर रूप से निर्धारित नहीं किया जा सकता है (अर्थात संकलन समय पर), इसलिए न तो संकलक यह तय कर सकता है कि उस समय कौन सा फलन कॉल करना है। कॉल को गतिशील रूप से (यानी, रन टाइम (प्रोग्राम लाइफसाइकिल चरण) पर) सही फलन पर भेजा जाना चाहिए।

कार्यान्वयन

ऑब्जेक्ट की वर्चुअल विधि तालिका में ऑब्जेक्ट की गतिशील रूप से बाध्य विधियों का मेमोरी पता होगा है। ऑब्जेक्ट के वर्चुअल मेथड टेबल से मेथड का पता लाकर मेथड कॉल की जाती है। वर्चुअल विधि तालिका एक ही वर्ग से संबंधित सभी वस्तुओं के लिए समान होती है, और इसलिए सामान्यतः उनके बीच साझा की जाती है। प्रकार-संगत वर्गों से संबंधित वस्तुएं (उदाहरण के लिए वंशानुक्रम पदानुक्रम में सिब्लिंग्स) एक ही लेआउट के साथ वर्चुअल विधि तालिकाएँ होंगी: किसी दिए गए विधि का पता सभी प्रकार-संगत वर्गों के लिए समान अनुचित्रण पर दिखाई देगा। इस प्रकार, किसी दिए गए अनुचित्रण से वर्चुअल विधि तालिका में विधि का पता लाने से ऑब्जेक्ट की वास्तविक कक्षा से संबंधित विधि मिल जाएगी।[1]

C ++ मानकों को अनिवार्य नहीं है कि गतिशील प्रेषण को कैसे कार्यान्वित किया जाना चाहिए, लेकिन कंपाइलर्स सामान्यतः एक ही मूल प्रतिरूप पर सामान्य बदलाव का उपयोग करते हैं।

सामान्यतः, कंपाइलर प्रत्येक वर्ग के लिए एक अलग वर्चुअल मेथड टेबल बनाता है। जब कोई ऑब्जेक्ट बनाया जाता है, तो इस तालिका में एक पॉइंटर, जिसे वर्चुअल टेबल पॉइंटर, वीपॉइंटर या वीपीटीआर कहा जाता है, इस ऑब्जेक्ट के छिपे हुए सदस्य के रूप में जोड़ा जाता है। जैसे, कंपाइलर को प्रत्येक वर्ग के कंस्ट्रक्टर (ऑब्जेक्ट-ओरिएंटेड प्रोग्रामिंग) में छिपे हुए कोड को भी उत्पन्न करना चाहिए ताकि एक नए ऑब्जेक्ट के वर्चुअल टेबल पॉइंटर को उसके क्लास के वर्चुअल मेथड टेबल के पते पर आवाक्षरित किया जा सके।

कई कंपाइलर वर्चुअल टेबल पॉइंटर को ऑब्जेक्ट के अंतिम सदस्य के रूप में रखते हैं; अन्य संकलक इसे पहले स्थान पर रखते हैं; सुवाह्य स्रोत कोड किसी भी तरह से काम करता है।[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 वस्तु के लिए निम्नलिखित b232-बिट मेमोरी लेआउट उत्पन्न करता है: [nb 1] <पूर्व> बी 2:

B2 की वर्चुअल विधि तालिका:

 b2:
  +0: pointer to virtual method table of B2
  +4: value of int_in_b2

virtual method table of B2:
  +0: B2::f2()   

और ऑब्जेक्ट के लिए निम्न मेमोरी लेआउट d:

 d:
  +0: pointer to virtual method table of D (for B1)
  +4: value of int_in_b1
  +8: pointer to virtual method table of D (for B2)
 +12: value of int_in_b2
 +16: value of int_in_d

Total size: 20 Bytes.

virtual method table of D (for B1):
  +0: B1::f1()  // B1::f1() is not overridden

virtual method table of D (for B2):
  +0: D::f2()   // B2::f2() is overridden by D::f2()
                // The location of B2::f2 is not in the virtual method table for D

ध्यान दें कि वे कार्य virtualकीवर्ड नहीं ले रहे हैं उनकी घोषणा में (जैसे fnonvirtual() और d()) सामान्यतः वर्चुअल मेथड टेबल में दिखाई नहीं देते हैं। डिफ़ॉल्ट कंस्ट्रक्टर द्वारा उत्पन्न विशेष स्तिथियों के अपवाद हैं।

वर्चुअल फलन में वर्चुअल डिस्ट्रक्टर्स B1 और B2पर भी ध्यान दें। उन्हें सुनिश्चित करना आवश्यक है delete d न केवल स्मृति Dको मुक्त कर सकते हैं, बल्कि B1 और B2 के लिए भी मुक्त कर सकते हैं, अगर d एक सूचक या प्रकार का संदर्भ B1 या B2 है। उदाहरण को सरल रखने के लिए उन्हें मेमोरी लेआउट से बाहर रखा गया था। [nb 2]

ओवरराइडिंग विधि f2() कक्षा में D की वर्चुअल विधि तालिका को प्रतिलिपि करके कार्यान्वित किया जाता है और B2सूचक की जगह B2::f2() एक सूचक D::f2() के साथ है।

एकाधिक वंशानुक्रम और थंक्स

G++ कंपाइलर प्रत्येक बेस क्लास के लिए दो वर्चुअल मेथड टेबल का उपयोग करके क्लास D में क्लास B1और B2 के विविध इनहेरिटेंस को लागू करता है। (विविध इनहेरिटेंस को लागू करने के अन्य तरीके हैं, लेकिन यह सबसे सामान्य है।) यह पॉइंटर फिक्सअप की आवश्यकता की ओर ले जाता है, जिसे थंक (प्रोग्रामिंग) भी कहा जाता है, जब टाइप रूपांतरण होता है।

निम्नलिखित सी ++ कोड पर विचार करें:

D  *d  = new D();
B1 *b1 = d;
B2 *b2 = d;

जबकि d और b1 इस कोड के निष्पादन के बाद उसी स्मृति स्थान को इंगित करेगा, b2 स्थान की ओर संकेत करेगा d+8 (आठ बाइट्स की स्मृति स्थान से परे d)। इस प्रकार, b2 भीतर के क्षेत्र की ओर इशारा करता है d का एक उदाहरण B2 लगता है, यानी, एक उदाहरण के रूप में एक ही मेमोरी लेआउट B2 है।

आघोष

d->f1()को एक कॉल अपसंदर्भन द्वारा नियंत्रित किया जाता है d'एस D::B1 वीपॉइंटर, ऊपर f1देख रहा है। वर्चुअल मेथड टेबल में एंट्री, और फिर कोड को कॉल करने के लिए उस पॉइंटर को अपसंदर्भन करना है।

एकल वंशानुक्रम

एकल वंशानुक्रम (या केवल एकल वंशानुक्रम वाली भाषा में) की स्तिथि में, यदि वीपॉइंटर हमेशा पहला तत्व d होता है (जैसा कि यह कई कंपाइलर्स के साथ है), यह निम्न छद्म-C ++ को कम करता है:

(*((*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] आधुनिक कार्यों पर वर्चुअल कार्यों सीपीयू की लागत इतनी अधिक नहीं हो सकती है, बहुत बड़े कैश और बेहतर ब्रांच प्रेडिक्शन के कारण आर्किटेक्चर है।

इसके अतिरिक्त, ऐसे वातावरण में जहां समय-समय पर संकलन उपयोग में नहीं है, वर्चुअल फलन कॉल सामान्यतः इनलाइन विस्तार नहीं हो सकते हैं। कुछ स्तिथियों में संकलक के लिए यह संभव हो सकता है कि वह डिवर्चुअलाइज़ेशन के रूप में जानी जाने वाली प्रक्रिया को अंजाम दे, जिसमें उदाहरण के लिए, लुकअप और अप्रत्यक्ष कॉल को प्रत्येक इनलाइन बॉडी के सशर्त निष्पादन के साथ बदल दिया जाता है, लेकिन ऐसे अनुकूलन सामान्य नहीं हैं।

इस शिरोपरि से बचने के लिए, जब भी संकलन समय पर कॉल को हल किया जा सकता है, तो कंपाइलर सामान्यतः वर्चुअल मेथड टेबल का उपयोग करने से बचते हैं।

इस प्रकार, उपरोक्त f1 पर कॉल के लिए तालिका लुकअप की आवश्यकता नहीं हो सकती है क्योंकि कंपाइलर यह बताने में सक्षम हो सकता है कि d केवल इस बिंदु पर D को पकड़ सकता है, और D, f1 को ओवरराइड नहीं करता है। या कंपाइलर (या ऑप्टिमाइज़र) यह पता लगाने में सक्षम हो सकता है कि प्रोग्राम में कहीं भी B1 का कोई उपवर्ग नहीं है जो f1 को ओवरराइड करता हो। B1::f1 या B2::f2 पर कॉल के लिए संभवतः तालिका लुकअप की आवश्यकता नहीं होगी क्योंकि कार्यान्वयन स्पष्ट रूप से निर्दिष्ट है (हालांकि इसे अभी भी 'यह'-पॉइंटर फ़िक्सअप की आवश्यकता है)।

विकल्पों के साथ तुलना

डायनेमिक डिस्पैच प्राप्त करने के लिए वर्चुअल मेथड टेबल सामान्यतः एक अच्छा प्रदर्शन ट्रेड-ऑफ है, लेकिन उच्च प्रदर्शन के साथ बाइनरी ट्री डिस्पैच जैसे विकल्प हैं, लेकिन अलग-अलग लागतें हैं। [5] हालाँकि, वर्चुअल मेथड टेबल केवल विशेष मापदण्ड पर एकल प्रेषण की अनुमति देते हैं, एकाधिक प्रेषण (कॉमन लिस्प ऑब्जेक्ट सिस्टम, डायलन (प्रोग्रामिंग भाषा) , या जूलिया (प्रोग्रामिंग भाषा)) के विपरीत, जहां सभी प्रकार के मापदण्ड हो सकते हैं भेजने में ध्यान में रखा जाना चाहिए।

वर्चुअल मेथड टेबल भी तभी काम करते हैं जब डिस्पैचिंग विधियों के ज्ञात सम्मुच्चय तक सीमित हो, इसलिए उन्हें डक टाइपिंग लैंग्वेज (जैसे स्मॉलटाक, पायथन (प्रोग्रामिंग लैंग्वेज) या जावास्क्रिप्ट) के विपरीत संकलन समय पर निर्मित एक साधारण सरणी में रखा जा सकता है।

ऐसी भाषाएँ जो इनमें से कोई एक या दोनों सुविधाएँ प्रदान करती हैं, प्रायः हैश तालिका में एक स्ट्रिंग या किसी अन्य समकक्ष विधि को देखकर प्रेषित होती हैं। इसे तेज़ बनाने के लिए कई तरह की तकनीकें हैं (उदाहरण के लिए, स्ट्रिंग इंटर्निंग/टोकनिंग विधि नाम, कैशिंग लुकअप, समय-समय पर संकलन)।

यह भी देखें

टिप्पणियाँ

  1. 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.
  2. "C++ - why there are two virtual destructor in the virtual table and where is address of the non-virtual function (gcc4.6.3)".


संदर्भ

  1. Ellis & Stroustrup 1990, pp. 227–232
  2. Danny Kalev. "C++ Reference Guide: The Object Model II". 2003. Heading "Inheritance and Polymorphism" and "Multiple Inheritance".
  3. "सी ++ एबीआई बंद मुद्दे". Archived from the original on 25 July 2011. Retrieved 17 June 2011.{{cite web}}: CS1 maint: bot: original URL status unknown (link)
  4. Driesen, Karel and Hölzle, Urs, "The Direct Cost of Virtual Function Calls in C++", OOPSLA 1996
  5. 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)