आभासी कार्य और व्यवहार्य कैसे लागू होते हैं?


109

हम सभी जानते हैं कि C ++ में कौन से वर्चुअल फ़ंक्शंस हैं, लेकिन उन्हें गहरे स्तर पर कैसे लागू किया जाता है?

क्या वीटबल को संशोधित किया जा सकता है या यहां तक ​​कि सीधे रनटाइम पर भी पहुँचा जा सकता है?

क्या सभी वर्गों के लिए वाइटेबल मौजूद है, या केवल वे ही हैं जिनके पास कम से कम एक वर्चुअल फ़ंक्शन है?

क्या अमूर्त कक्षाओं में केवल कम से कम एक प्रविष्टि के फ़ंक्शन पॉइंटर के लिए NULL है?

क्या एक ही वर्चुअल फंक्शन पूरी क्लास को धीमा कर देता है? या केवल फ़ंक्शन के लिए कॉल वर्चुअल है? और क्या गति प्रभावित हो जाती है अगर आभासी फ़ंक्शन वास्तव में ओवरराइट किया गया है या नहीं, या क्या इसका कोई प्रभाव नहीं है जब तक कि यह आभासी है।


2
Inside the C++ Object Modelद्वारा कृति पढ़ने का सुझाव दें Stanley B. Lippman। (धारा 4.2, पृष्ठ 124-131)
smwikipedia

जवाबों:


123

आभासी कार्यों को एक गहरे स्तर पर कैसे लागू किया जाता है?

से "C ++ वर्चुअल प्रकार्य" :

जब भी किसी प्रोग्राम में वर्चुअल फंक्शन घोषित किया जाता है, तो एवी - क्लास का निर्माण किया जाता है। V- तालिका में एक या अधिक वर्चुअल फ़ंक्शंस वाले वर्गों के लिए वर्चुअल फ़ंक्शंस के पते होते हैं। वर्चुअल फ़ंक्शन वाले वर्ग के ऑब्जेक्ट में एक वर्चुअल पॉइंटर होता है जो मेमोरी में वर्चुअल टेबल के आधार पते को इंगित करता है। जब भी कोई वर्चुअल फ़ंक्शन कॉल होता है, तो v- टेबल का उपयोग फ़ंक्शन एड्रेस को हल करने के लिए किया जाता है। कक्षा की एक वस्तु जिसमें एक या एक से अधिक वर्चुअल फ़ंक्शंस होते हैं, जिसमें मेमोरी में ऑब्जेक्ट की शुरुआत में vptr नामक एक वर्चुअल पॉइंटर होता है। इसलिए इस मामले में ऑब्जेक्ट का आकार पॉइंटर के आकार से बढ़ता है। इस vptr में मेमोरी में वर्चुअल टेबल का आधार पता होता है। ध्यान दें कि वर्चुअल टेबल विशिष्ट हैं, अर्थात इसमें शामिल वर्चुअल कार्यों की संख्या के बावजूद एक वर्ग के लिए केवल एक वर्चुअल टेबल है। बदले में इस आभासी तालिका में कक्षा के एक या अधिक आभासी कार्यों के आधार पते शामिल हैं। जिस समय किसी ऑब्जेक्ट पर वर्चुअल फ़ंक्शन को कॉल किया जाता है, उस ऑब्जेक्ट का vptr मेमोरी में उस क्लास के लिए वर्चुअल टेबल का आधार पता प्रदान करता है। इस तालिका का उपयोग फ़ंक्शन कॉल को हल करने के लिए किया जाता है क्योंकि इसमें उस वर्ग के सभी आभासी कार्यों के पते शामिल हैं। वर्चुअल फ़ंक्शन कॉल के दौरान डायनामिक बाइंडिंग को कैसे हल किया जाता है। उस ऑब्जेक्ट का vptr मेमोरी में उस क्लास के लिए वर्चुअल टेबल का आधार पता प्रदान करता है। इस तालिका का उपयोग फ़ंक्शन कॉल को हल करने के लिए किया जाता है क्योंकि इसमें उस वर्ग के सभी आभासी कार्यों के पते शामिल हैं। वर्चुअल फ़ंक्शन कॉल के दौरान डायनामिक बाइंडिंग को कैसे हल किया जाता है। उस ऑब्जेक्ट का vptr मेमोरी में उस क्लास के लिए वर्चुअल टेबल का आधार पता प्रदान करता है। इस तालिका का उपयोग फ़ंक्शन कॉल को हल करने के लिए किया जाता है क्योंकि इसमें उस वर्ग के सभी आभासी कार्यों के पते होते हैं। वर्चुअल फ़ंक्शन कॉल के दौरान डायनामिक बाइंडिंग को कैसे हल किया जाता है।

क्या वीटबल को संशोधित किया जा सकता है या यहां तक ​​कि सीधे रनटाइम पर भी पहुँचा जा सकता है?

विश्वविद्यालय, मेरा मानना ​​है कि उत्तर "नहीं" है। आप वीभत्स खोजने के लिए कुछ मेमोरी मैनबलिंग कर सकते हैं लेकिन आपको अभी भी यह नहीं पता होगा कि फंक्शन सिग्नेचर इसे कॉल करने के लिए कैसा दिखता है। जो कुछ भी आप इस क्षमता (कि भाषा का समर्थन करता है) के साथ प्राप्त करना चाहते हैं, वह सीधे वाइबेट तक पहुंच के बिना या रनटाइम पर इसे संशोधित करने के बिना संभव होना चाहिए। यह भी ध्यान दें, C ++ भाषा कल्पना निर्दिष्ट नहीं करती है कि vtables आवश्यक हैं - हालांकि यह है कि अधिकांश कंपाइलर आभासी कार्यों को कैसे लागू करते हैं।

क्या सभी वस्तुओं के लिए वाइटबल मौजूद है, या केवल वे ही हैं जिनके पास कम से कम एक वर्चुअल फ़ंक्शन है?

मेरा मानना ​​है कि उत्तर "यह कार्यान्वयन पर निर्भर करता है" क्योंकि कल्पना को पहली जगह में vtables की आवश्यकता नहीं है। हालाँकि, व्यवहार में, मेरा मानना ​​है कि सभी आधुनिक कंपाइलर केवल एक विएबेट बनाते हैं यदि किसी वर्ग में कम से कम 1 वर्चुअल फ़ंक्शन हो। वहाँ एक अंतरिक्ष उपरि vtable के साथ जुड़ा हुआ है और एक समय पर उपरिव्यय एक आभासी समारोह बनाम एक गैर-आभासी फ़ंक्शन को कॉल करने से जुड़ा हुआ है।

क्या अमूर्त कक्षाओं में केवल कम से कम एक प्रविष्टि के फ़ंक्शन पॉइंटर के लिए NULL है?

इसका उत्तर यह है कि यह भाषा की कल्पना से अनिर्दिष्ट है इसलिए यह कार्यान्वयन पर निर्भर करता है। शुद्ध आभासी फ़ंक्शन को कॉल करने से अपरिभाषित व्यवहार होता है, अगर इसे परिभाषित नहीं किया गया है (जो आमतौर पर नहीं है) (आईएसओ / आईईसी 14882: 2003 10.4-2)। व्यवहार में यह फ़ंक्शन के लिए व्यवहार्यता में एक स्लॉट आवंटित करता है, लेकिन इसके लिए कोई पता निर्दिष्ट नहीं करता है। यह vtable को अधूरा छोड़ देता है जिससे फ़ंक्शन को लागू करने और vtable को पूरा करने के लिए व्युत्पन्न वर्गों की आवश्यकता होती है। कुछ कार्यान्वयन केवल एक पूर्ण सूचक को विटेबल प्रविष्टि में रखते हैं; अन्य कार्यान्वयन एक पॉइंटर को एक डमी विधि में रखते हैं जो एक दावे के समान होता है।

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

क्या एक ही वर्चुअल फंक्शन पूरे क्लास को धीमा कर देता है या केवल फंक्शन के लिए कॉल वर्चुअल है?

यह मेरी जानकारी के किनारे पर हो रहा है, इसलिए अगर कोई गलत है तो कृपया मेरी मदद करें!

मेरा मानना ​​है कि क्लास में वर्चुअल होने वाले फंक्शन केवल वर्चुअल फंक्शन बनाम नॉन-वर्चुअल फंक्शन कॉल करने से संबंधित टाइम परफॉर्मेंस हिट होते हैं। क्लास के लिए स्पेस ओवरहेड या तो रास्ता है। ध्यान दें कि यदि कोई व्यर्थता है, तो केवल 1 प्रति वर्ग है , प्रति वस्तु एक नहीं है ।

क्या वर्चुअल फ़ंक्शन वास्तव में ओवरराइड है या नहीं, या जब तक यह आभासी है तब तक इसका कोई प्रभाव नहीं पड़ता है तो गति प्रभावित होती है?

मुझे विश्वास नहीं है कि वर्चुअल फ़ंक्शन के निष्पादन का समय आधार वर्चुअल फ़ंक्शन को कॉल करने की तुलना में कम हो जाता है। हालांकि, आधार वर्ग बनाम आधार वर्ग के लिए एक और व्यवहार्यता को परिभाषित करने से जुड़े वर्ग के लिए एक अतिरिक्त स्थान है।

अतिरिक्त संसाधन:

http://www.codersource.net/published/view/325/virtual_functions_in.aspx (बैक मशीन के माध्यम से)
http://en.wikipedia.org/wiki/Virtual_table http
://www.codesourcery/public/ CXX-अबी / abi.html # vtable


2
यह संकलक के लिए सी ++ के स्ट्रॉस्ट्रुप के दर्शन के अनुरूप नहीं होगा, जो किसी वस्तु में अनावश्यक रूप से व्यवहार्य सूचक डालने के लिए है, जिसकी आवश्यकता नहीं है। नियम यह है कि जब तक आप इसके लिए नहीं पूछते हैं, तब तक आपको ओवरहेड नहीं मिलता है और जब तक कि यह टूटने के लिए कंपाइलरों के लिए अशिष्ट हो जाता है।
स्टीव जेसप

3
मैं मानता हूं कि किसी भी संकलक के लिए यह मूर्खता होगी जो किसी भी वर्चुअल फ़ंक्शन के मौजूद होने पर खुद को गंभीरता से उपयोग करने के लिए गंभीरता से लेता है। हालाँकि, मुझे यह बताना महत्वपूर्ण लगा कि, मेरी जानकारी के लिए, C ++ मानक की आवश्यकता नहीं है / इसकी आवश्यकता नहीं है, इसलिए इसके आधार पर पहले चेतावनी दी जाए।
जैच बर्लिंगम

8
यहां तक ​​कि आभासी कार्यों को गैर-वस्तुतः कहा जा सकता है। यह वास्तव में काफी सामान्य है: यदि ऑब्जेक्ट स्टैक पर है, तो दायरे में कंपाइलर सटीक प्रकार को जान लेगा और वाइबेट लुकअप को ऑप्टिमाइज़ कर देगा। यह डॉटर के लिए विशेष रूप से सच है, जिसे एक ही स्टैक स्कोप में बुलाया जाना चाहिए।
MSalters

1
मेरा मानना ​​है कि जब एक वर्ग जिसमें कम से कम एक आभासी कार्य होता है, तो प्रत्येक वस्तु में एक व्यवहार्य होता है, और पूरे वर्ग के लिए नहीं।
आसफ आर

3
आम कार्यान्वयन: प्रत्येक वस्तु में एक सूचक के लिए एक सूचक है; वर्ग तालिका का मालिक है। बेस ctor समाप्त हो जाने के बाद, निर्माण जादू बस व्युत्पन्न ctor में vtable सूचक को अद्यतन करने के होते हैं।
12

31
  • क्या वीटबल को संशोधित किया जा सकता है या यहां तक ​​कि सीधे रनटाइम पर भी पहुँचा जा सकता है?

निश्चित रूप से नहीं, लेकिन अगर आप गंदे चालों को बुरा नहीं मानते हैं, तो निश्चित रूप से!

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

अधिकांश संकलक में मैंने देखा है, vtbl * ऑब्जेक्ट की पहली 4 बाइट्स है, और vtbl सामग्री केवल सदस्य पॉइंटर्स की एक सरणी है (आमतौर पर क्रम में वे घोषित किए गए थे, बेस क्लास के पहले के साथ)। बेशक अन्य संभावित लेआउट हैं, लेकिन यही वह है जो मैंने आमतौर पर देखा है।

class A {
  public:
  virtual int f1() = 0;
};
class B : public A {
  public:
  virtual int f1() { return 1; }
  virtual int f2() { return 2; }
};
class C : public A {
  public:
  virtual int f1() { return -1; }
  virtual int f2() { return -2; }
};

A *x = new B;
A *y = new C;
A *z = new C;

अब कुछ शीनिगन्स को खींचने के लिए ...

रनटाइम पर क्लास बदलना:

std::swap(*(void **)x, *(void **)y);
// Now x is a C, and y is a B! Hope they used the same layout of members!

सभी उदाहरणों के लिए एक विधि को बदलना (एक वर्ग को बंद करना)

यह एक छोटा सा पेचीदा मामला है, क्योंकि vtbl खुद शायद केवल पढ़ने वाली मेमोरी में है।

int f3(A*) { return 0; }

mprotect(*(void **)x,8,PROT_READ|PROT_WRITE|PROT_EXEC);
// Or VirtualProtect on win32; this part's very OS-specific
(*(int (***)(A *)x)[0] = f3;
// Now C::f1() returns 0 (remember we made x into a C above)
// so x->f1() and z->f1() both return 0

बाद वाले को वायरस-चेकर्स बनाने की संभावना है और लिंक में गड़बड़ी होने के कारण, जागने और नोटिस लेने की संभावना है। NX बिट का उपयोग करने की प्रक्रिया में यह अच्छी तरह से विफल हो सकता है।


6
हम्म। यह अशुभ लगता है कि यह एक इनाम प्राप्त हुआ। मुझे उम्मीद है कि इसका मतलब यह नहीं है कि @Mobilewits को लगता है कि इस तरह के शीनिगान वास्तव में एक अच्छा विचार हैं ...
15:15 बजे 15:15

1
कृपया इस तकनीक के उपयोग को हतोत्साहित करने पर विचार करें, स्पष्ट रूप से और दृढ़ता से, बजाय "पलक" के।
einpoklum

" vtbl सामग्री केवल सदस्य बिंदुओं की एक सरणी है " वास्तव में यह विभिन्न प्रविष्टियों के साथ एक रिकॉर्ड (एक संरचना) है, जो समान रूप से दूरी पर होती है
curiousguy

1
आप इसे किसी भी तरह से देख सकते हैं; फ़ंक्शन पॉइंटर्स के अलग-अलग हस्ताक्षर हैं, और इस प्रकार अलग-अलग पॉइंटर प्रकार हैं; उस अर्थ में, यह वास्तव में संरचना की तरह है। लेकिन अन्य संदर्भों में, लेकिन vtbl सूचकांक का विचार उपयोगी है (उदाहरण के लिए ActiveX इसका उपयोग टाइपेलिब में दोहरे इंटरफेस का वर्णन करता है), जो एक अधिक सरणी जैसा दृश्य है।
puetzk

17

क्या एक ही वर्चुअल फंक्शन पूरी क्लास को धीमा कर देता है?

या केवल फ़ंक्शन के लिए कॉल वर्चुअल है? और क्या गति प्रभावित हो जाती है अगर आभासी फ़ंक्शन वास्तव में ओवरराइट किया गया है या नहीं, या क्या इसका कोई प्रभाव नहीं है जब तक कि यह आभासी है।

वर्चुअल फ़ंक्शंस होने से पूरी क्लास का इंफ़ॉसर धीमा पड़ जाता है क्योंकि डेटा के एक और आइटम को ऐसी क्लास के ऑब्जेक्ट से डील करते समय इनिशियलाइज़, कॉपी,… करना पड़ता है। आधा दर्जन सदस्यों के साथ एक वर्ग के लिए, अंतर नगण्य होना चाहिए। एक ऐसे वर्ग के लिए जिसमें सिर्फ एक ही charसदस्य होता है, या कोई भी सदस्य नहीं होता है, अंतर उल्लेखनीय हो सकता है।

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

struct Foo { virtual ~Foo(); virtual int a() { return 1; } };
struct Bar: public Foo { int a() { return 2; } };
void f(Foo& arg) {
  Foo x; x.a(); // non-virtual: always calls Foo::a()
  Bar y; y.a(); // non-virtual: always calls Bar::a()
  arg.a();      // virtual: must dispatch via vtable
  Foo z = arg;  // copy constructor Foo::Foo(const Foo&) will convert to Foo
  z.a();        // non-virtual Foo::a, since z is a Foo, even if arg was not
}

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

वे एक गहरे स्तर पर कैसे लागू होते हैं?

मुझे एक नकली कार्यान्वयन का उपयोग करके इसे प्रदर्शित करने के लिए जेरिको का विचार पसंद है। लेकिन मैं C का उपयोग कुछ कोड को ऊपर के कोड को लागू करने के लिए करूंगा, ताकि निम्न स्तर अधिक आसानी से दिखाई दे।

अभिभावक वर्ग फू

typedef struct Foo_t Foo;   // forward declaration
struct slotsFoo {           // list all virtual functions of Foo
  const void *parentVtable; // (single) inheritance
  void (*destructor)(Foo*); // virtual destructor Foo::~Foo
  int (*a)(Foo*);           // virtual function Foo::a
};
struct Foo_t {                      // class Foo
  const struct slotsFoo* vtable;    // each instance points to vtable
};
void destructFoo(Foo* self) { }     // Foo::~Foo
int aFoo(Foo* self) { return 1; }   // Foo::a()
const struct slotsFoo vtableFoo = { // only one constant table
  0,                                // no parent class
  destructFoo,
  aFoo
};
void constructFoo(Foo* self) {      // Foo::Foo()
  self->vtable = &vtableFoo;        // object points to class vtable
}
void copyConstructFoo(Foo* self,
                      Foo* other) { // Foo::Foo(const Foo&)
  self->vtable = &vtableFoo;        // don't copy from other!
}

व्युत्पन्न वर्ग बार

typedef struct Bar_t {              // class Bar
  Foo base;                         // inherit all members of Foo
} Bar;
void destructBar(Bar* self) { }     // Bar::~Bar
int aBar(Bar* self) { return 2; }   // Bar::a()
const struct slotsFoo vtableBar = { // one more constant table
  &vtableFoo,                       // can dynamic_cast to Foo
  (void(*)(Foo*)) destructBar,      // must cast type to avoid errors
  (int(*)(Foo*)) aBar
};
void constructBar(Bar* self) {      // Bar::Bar()
  self->base.vtable = &vtableBar;   // point to Bar vtable
}

फंक्शन वर्चुअल फंक्शन कॉल करते हुए

void f(Foo* arg) {                  // same functionality as above
  Foo x; constructFoo(&x); aFoo(&x);
  Bar y; constructBar(&y); aBar(&y);
  arg->vtable->a(arg);              // virtual function call
  Foo z; copyConstructFoo(&z, arg);
  aFoo(&z);
  destructFoo(&z);
  destructBar(&y);
  destructFoo(&x);
}

तो आप देख सकते हैं, एक वायबल सिर्फ मेमोरी में एक स्थिर ब्लॉक है, जिसमें ज्यादातर फ़ंक्शन पॉइंटर्स हैं। एक बहुरूपिए वर्ग की प्रत्येक वस्तु अपने गतिशील प्रकार के अनुरूप व्यवहार्य को इंगित करेगी। यह RTTI और वर्चुअल फ़ंक्शंस के बीच संबंध को भी स्पष्ट करता है: आप यह देख सकते हैं कि किस प्रकार की कक्षा बस यह देखती है कि यह किस बिंदु पर है। ऊपर कई तरीकों से सरलीकृत किया गया है, जैसे कि एकाधिक विरासत, लेकिन सामान्य अवधारणा ध्वनि है।

यदि argप्रकार का है Foo*और आप लेते हैं arg->vtable, लेकिन वास्तव में प्रकार की वस्तु है Bar, तो आपको अभी भी इसका सही पता मिलता है vtable। ऐसा इसलिए है क्योंकि vtableऑब्जेक्ट के पते पर हमेशा पहला तत्व होता है, चाहे वह किसी भी तरह का हो vtableया base.vtableसही-सही टाइप किया गया हो।


"एक बहुरूपिए वर्ग की प्रत्येक वस्तु अपने स्वयं के व्यवहार्य की ओर इशारा करेगी।" क्या आप कह रहे हैं कि हर वस्तु की अपनी एक अलग पहचान है? AFAIK vtable एक ही वर्ग के सभी ऑब्जेक्ट्स के बीच साझा किया जाता है। अगर मैं गलत हूं तो मुझे बताएं।
भुवन

1
@ भुवन: नहीं, आप सही कह रहे हैं: प्रति प्रकार केवल एक व्यवहार्य है (जो टेम्पलेट के मामले में प्रति टेम्पलेट तात्कालिकता हो सकती है)। मेरा कहने का मतलब यह था कि पॉलीमॉर्फिक क्लास की प्रत्येक वस्तु उस पर लागू होने वाले वाइबेट की ओर इशारा करती है, इसलिए प्रत्येक ऑब्जेक्ट में ऐसा पॉइंटर होता है, लेकिन एक ही प्रकार की वस्तुओं के लिए यह एक ही टेबल की ओर इशारा करेगा। संभवत: मुझे इसका पुन: प्रकाशन करना चाहिए।
MvG

1
@MvG " एक ही प्रकार की वस्तुएं एक ही टेबल की ओर इंगित करेंगी " वर्चुअल बेस कक्षाओं के साथ बेस कक्षाओं के निर्माण के दौरान नहीं! (एक बहुत ही खास मामला)
क्यूरियस गुय

1
@ कुरसी: मुझे लगता है कि "ऊपर कई तरीकों से सरलीकृत किया गया है" के तहत फ़ाइल, विशेष रूप से आभासी ठिकानों के मुख्य आवेदन कई विरासत है, जो मैं या तो मॉडल नहीं किया था। लेकिन टिप्पणी के लिए धन्यवाद, यहां उन लोगों के लिए उपयोगी है, जिन्हें अधिक गहराई की आवश्यकता हो सकती है।
MvG


2

इस उत्तर को सामुदायिक विकी उत्तर में शामिल किया गया है

  • क्या अमूर्त कक्षाओं में केवल कम से कम एक प्रविष्टि के फ़ंक्शन पॉइंटर के लिए NULL है?

इसका उत्तर यह है कि यह अनिर्दिष्ट है - शुद्ध आभासी फ़ंक्शन को अपरिभाषित व्यवहार में परिणत करने पर यदि इसे परिभाषित नहीं किया जाता है (जो आमतौर पर नहीं होता है) (ISO / IEC 14882: 2003 10.4-2)। कुछ कार्यान्वयन केवल एक पूर्ण सूचक को विटेबल प्रविष्टि में रखते हैं; अन्य कार्यान्वयन एक पॉइंटर को एक डमी विधि में रखते हैं जो एक दावे के समान होता है।

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


इसके अलावा, मुझे नहीं लगता कि एक सार वर्ग एक शुद्ध आभासी फ़ंक्शन के लिए कार्यान्वयन को परिभाषित कर सकता है। विक्षेपण द्वारा, एक शुद्ध आभासी फ़ंक्शन का कोई शरीर नहीं है (उदाहरण के लिए बूल my_func () = 0;)। हालाँकि, आप नियमित वर्चुअल फ़ंक्शंस के लिए कार्यान्वयन प्रदान कर सकते हैं।
जैच बर्लिंगम

एक शुद्ध आभासी फ़ंक्शन की एक परिभाषा हो सकती है। स्कॉट मेयर्स का "प्रभावी सी ++, तीसरा एड" आइटम # 34, आईएसओ 14882-2003 10.4-2, या बाइट्स देखें /forum/thread572745.html
माइकल

2

आप फ़ंक्शन ++ के उपयोग से C ++ में वर्चुअल फ़ंक्शंस की कार्यक्षमता को एक वर्ग के सदस्यों के रूप में और कार्यान्वयन के रूप में स्थिर कार्यों को लागू कर सकते हैं, या कार्यान्वयन के लिए सदस्य फ़ंक्शंस और सदस्य कार्यों के लिए सूचक का उपयोग कर सकते हैं। दो तरीकों के बीच केवल नोटिफिकेशंस फायदे हैं ... वास्तव में वर्चुअल फंक्शन कॉल्स सिर्फ एक उल्लेखनीय सुविधा है। वास्तव में वंशानुक्रम सिर्फ एक उल्लेखनीय सुविधा है ... यह सब विरासत के लिए भाषा की विशेषताओं का उपयोग किए बिना लागू किया जा सकता है। :)

नीचे बकवास है, शायद छोटी गाड़ी कोड है, लेकिन उम्मीद है कि विचार प्रदर्शित करता है।

जैसे

class Foo
{
protected:
 void(*)(Foo*) MyFunc;
public:
 Foo() { MyFunc = 0; }
 void ReplciatedVirtualFunctionCall()
 {
  MyFunc(*this);
 }
...
};

class Bar : public Foo
{
private:
 static void impl1(Foo* f)
 {
  ...
 }
public:
 Bar() { MyFunc = impl1; }
...
};

class Baz : public Foo
{
private:
 static void impl2(Foo* f)
 {
  ...
 }
public:
 Baz() { MyFunc = impl2; }
...
};

void(*)(Foo*) MyFunc;क्या यह कुछ जावा सिंटैक्स है?
जिज्ञासु

नहीं, इसके सी / सी ++ फ़ंक्शन पॉइंटर्स के लिए सिंटैक्स। अपने आप को उद्धृत करने के लिए "आप फ़ंक्शन पॉइंटर्स का उपयोग करके C ++ में वर्चुअल फ़ंक्शन की कार्यक्षमता को फिर से बना सकते हैं"। इसके एक छोटे से वाक्यविन्यास, लेकिन कुछ के साथ परिचित होने के लिए अगर आप अपने आप को एक सी प्रोग्रामर मानते हैं।
जेरिको

एसी फ़ंक्शन पॉइंटर अधिक दिखेगा: int ( PROC) (); और एक कक्षा के सदस्य समारोह के लिए एक संकेतक इस तरह दिखेगा: int (ClassName :: MPROC) ();
खतरा

1
@ मेनस, आप वहां कुछ सिंटैक्स भूल गए ... आप टाइप्डफ के बारे में सोच रहे होंगे? typedef int (* PROC) (); इसलिए आप सिर्फ int (* foo) () के बजाय PROC foo बाद में कर सकते हैं?
जेरिको 15

2

मैं इसे सरल बनाने की कोशिश करूँगा :)

हम सभी जानते हैं कि C ++ में कौन से वर्चुअल फ़ंक्शंस हैं, लेकिन उन्हें गहरे स्तर पर कैसे लागू किया जाता है?

यह कार्य करने के लिए संकेत के साथ एक सरणी है, जो एक विशेष आभासी फ़ंक्शन के कार्यान्वयन हैं। इस सरणी में एक सूचकांक एक वर्ग के लिए परिभाषित आभासी फ़ंक्शन के विशेष सूचकांक का प्रतिनिधित्व करता है। इसमें शुद्ध आभासी कार्य शामिल हैं।

जब एक बहुरूपी वर्ग किसी अन्य बहुरूपी वर्ग से निकलता है, तो हमारे पास निम्नलिखित परिस्थितियाँ हो सकती हैं:

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

क्या वीटबल को संशोधित किया जा सकता है या यहां तक ​​कि सीधे रनटाइम पर भी पहुँचा जा सकता है?

मानक तरीका नहीं - उन्हें एक्सेस करने के लिए कोई एपीआई नहीं है। कंपाइलर्स के पास उन्हें एक्सेस करने के लिए कुछ एक्सटेंशन या निजी API हो सकते हैं, लेकिन यह केवल एक एक्सटेंशन हो सकता है।

क्या सभी वर्गों के लिए वाइटेबल मौजूद है, या केवल वे ही हैं जिनके पास कम से कम एक वर्चुअल फ़ंक्शन है?

केवल वे जिनके पास कम से कम एक आभासी कार्य है (चाहे वह विध्वंसक हो) या कम से कम एक वर्ग प्राप्त करें जिसमें इसकी व्यवहार्यता है ("पॉलीमॉर्फिक")।

क्या अमूर्त कक्षाओं में केवल कम से कम एक प्रविष्टि के फ़ंक्शन पॉइंटर के लिए NULL है?

यह एक संभव कार्यान्वयन है, लेकिन अभ्यास नहीं है। इसके बजाय आम तौर पर एक फ़ंक्शन होता है जो "शुद्ध आभासी फ़ंक्शन" नामक कुछ प्रिंट करता है और करता है abort()। यदि आप कंस्ट्रक्टर या डिस्ट्रक्टर में अमूर्त पद्धति को कॉल करने का प्रयास करते हैं, तो यह कॉल हो सकती है।

क्या एक ही वर्चुअल फंक्शन पूरी क्लास को धीमा कर देता है? या केवल फ़ंक्शन के लिए कॉल वर्चुअल है? और क्या गति प्रभावित हो जाती है अगर आभासी फ़ंक्शन वास्तव में ओवरराइट किया गया है या नहीं, या क्या इसका कोई प्रभाव नहीं है जब तक कि यह आभासी है।

मंदी केवल इस बात पर निर्भर करती है कि कॉल को डायरेक्ट कॉल के रूप में हल किया गया है या वर्चुअल कॉल के रूप में। और कोई मामलें नहीं हैं। :)

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

  • यदि आप विधि को एक मान के माध्यम से कहते हैं (एक फ़ंक्शन का एक चर या परिणाम जो एक मान लौटाता है) - इस मामले में संकलक को कोई संदेह नहीं है कि वस्तु का वास्तविक वर्ग क्या है, और संकलन समय पर इसे "हार्ड-हल" कर सकता है। ।
  • यदि वर्चुअल पद्धति को finalउस कक्षा में घोषित किया जाता है, जिसके पास आपके पास एक संकेतक या संदर्भ है जिसके माध्यम से आप इसे कॉल करते हैं ( केवल C ++ 11 में )। इस मामले में कंपाइलर जानता है कि यह विधि किसी भी अधिक ओवरराइडिंग से नहीं गुजर सकती है और यह केवल इस वर्ग की विधि हो सकती है।

ध्यान दें कि आभासी कॉलों में केवल दो बिंदुओं के डीफ्रैडिंग का ओवरहेड है। आरटीटीआई का उपयोग करना (हालांकि केवल पॉलीमॉर्फिक कक्षाओं के लिए उपलब्ध है) आभासी तरीकों को कॉल करने की तुलना में धीमा है, क्या आपको एक ही चीज़ को दो तरीकों से लागू करने के लिए एक मामला ढूंढना चाहिए। उदाहरण के लिए, परिभाषित करना virtual bool HasHoof() { return false; }और उसके बाद केवल ओवरराइड bool Horse::HasHoof() { return true; }करना आपको कॉल करने की क्षमता प्रदान if (anim->HasHoof())करेगा जो कि प्रयास करने से अधिक तेज़ होगा if(dynamic_cast<Horse*>(anim))। इसका कारण यह है कि dynamic_castकुछ मामलों में कक्षा पदानुक्रम के माध्यम से चलना पड़ता है यहां तक ​​कि यह देखने के लिए भी पुनरावृत्ति होती है कि क्या वास्तविक पॉइंटर प्रकार और वांछित वर्ग प्रकार से रास्ता बनाया जा सकता है। जबकि आभासी कॉल हमेशा एक ही होता है - दो बिंदुओं पर डेरेफ्रेंसिंग।


2

यहां आधुनिक C ++ में वर्चुअल टेबल का एक रन करने योग्य मैनुअल कार्यान्वयन है। इसमें अच्छी तरह से परिभाषित शब्दार्थ है, कोई हैक नहीं और नहीं void*

ध्यान दें: .*और ->*की तुलना में अलग ऑपरेटर हैं *और ->। सदस्य फ़ंक्शन पॉइंटर्स अलग तरीके से काम करते हैं।

#include <iostream>
#include <vector>
#include <memory>

struct vtable; // forward declare, we need just name

class animal
{
public:
    const std::string& get_name() const { return name; }

    // these will be abstract
    bool has_tail() const;
    bool has_wings() const;
    void sound() const;

protected: // we do not want animals to be created directly
    animal(const vtable* vtable_ptr, std::string name)
    : vtable_ptr(vtable_ptr), name(std::move(name)) { }

private:
    friend vtable; // just in case for non-public methods

    const vtable* const vtable_ptr;
    std::string name;
};

class cat : public animal
{
public:
    cat(std::string name);

    // functions to bind dynamically
    bool has_tail() const { return true; }
    bool has_wings() const { return false; }
    void sound() const
    {
        std::cout << get_name() << " does meow\n"; 
    }
};

class dog : public animal
{
public:
    dog(std::string name);

    // functions to bind dynamically
    bool has_tail() const { return true; }
    bool has_wings() const { return false; }
    void sound() const
    {
        std::cout << get_name() << " does whoof\n"; 
    }
};

class parrot : public animal
{
public:
    parrot(std::string name);

    // functions to bind dynamically
    bool has_tail() const { return false; }
    bool has_wings() const { return true; }
    void sound() const
    {
        std::cout << get_name() << " does crrra\n"; 
    }
};

// now the magic - pointers to member functions!
struct vtable
{
    bool (animal::* const has_tail)() const;
    bool (animal::* const has_wings)() const;
    void (animal::* const sound)() const;

    // constructor
    vtable (
        bool (animal::* const has_tail)() const,
        bool (animal::* const has_wings)() const,
        void (animal::* const sound)() const
    ) : has_tail(has_tail), has_wings(has_wings), sound(sound) { }
};

// global vtable objects
const vtable vtable_cat(
    static_cast<bool (animal::*)() const>(&cat::has_tail),
    static_cast<bool (animal::*)() const>(&cat::has_wings),
    static_cast<void (animal::*)() const>(&cat::sound));
const vtable vtable_dog(
    static_cast<bool (animal::*)() const>(&dog::has_tail),
    static_cast<bool (animal::*)() const>(&dog::has_wings),
    static_cast<void (animal::*)() const>(&dog::sound));
const vtable vtable_parrot(
    static_cast<bool (animal::*)() const>(&parrot::has_tail),
    static_cast<bool (animal::*)() const>(&parrot::has_wings),
    static_cast<void (animal::*)() const>(&parrot::sound));

// set vtable pointers in constructors
cat::cat(std::string name) : animal(&vtable_cat, std::move(name)) { }
dog::dog(std::string name) : animal(&vtable_dog, std::move(name)) { }
parrot::parrot(std::string name) : animal(&vtable_parrot, std::move(name)) { }

// implement dynamic dispatch
bool animal::has_tail() const
{
    return (this->*(vtable_ptr->has_tail))();
}

bool animal::has_wings() const
{
    return (this->*(vtable_ptr->has_wings))();
}

void animal::sound() const
{
    (this->*(vtable_ptr->sound))();
}

int main()
{
    std::vector<std::unique_ptr<animal>> animals;
    animals.push_back(std::make_unique<cat>("grumpy"));
    animals.push_back(std::make_unique<cat>("nyan"));
    animals.push_back(std::make_unique<dog>("doge"));
    animals.push_back(std::make_unique<parrot>("party"));

    for (const auto& a : animals)
        a->sound();

    // note: destructors are not dispatched virtually
}

1

प्रत्येक ऑब्जेक्ट में एक वाइबेटर पॉइंटर होता है जो सदस्य फ़ंक्शन की एक सरणी को इंगित करता है।


1

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


0

इस सवाल के सिवाय बूरी तरह से उत्तर सही हैं:

क्या अमूर्त कक्षाओं में केवल कम से कम एक प्रविष्टि के फ़ंक्शन पॉइंटर के लिए NULL है?

इसका उत्तर यह है कि अमूर्त वर्गों के लिए कोई आभासी तालिका नहीं बनाई गई है। इन वर्गों की कोई भी वस्तु नहीं बनाई जा सकती है, इसकी कोई आवश्यकता नहीं है!

दूसरे शब्दों में अगर हमारे पास:

class B { ~B() = 0; }; // Abstract Base class
class D : public B { ~D() {} }; // Concrete Derived class

D* pD = new D();
B* pB = pD;

PB के माध्यम से एक्सेस किया जाने वाला vtbl पॉइंटर, क्लास D का vtbl होगा। यह ठीक उसी प्रकार है जैसे बहुरूपता को लागू किया जाता है। यही है, डी बी के माध्यम से डी तरीकों तक कैसे पहुंचा जाता है। वर्ग बी के लिए एक vtbl की कोई आवश्यकता नहीं है।

नीचे माइक की टिप्पणी के जवाब में ...

यदि मेरे विवरण में B वर्ग में वर्चुअल विधि foo () है जो डी से ओवरराइड नहीं है और एक वर्चुअल विधि बार () ओवरराइड है, तो डी के vtbl में बी के फू () और अपने स्वयं के बार () के लिए एक पॉइंटर होगा। । अभी भी B के लिए कोई vtbl नहीं बना है


यह 2 कारणों से सही नहीं है: 1) एक अमूर्त वर्ग में शुद्ध आभासी विधियों के अलावा नियमित आभासी विधियां हो सकती हैं, और 2) शुद्ध आभासी विधियों में वैकल्पिक रूप से एक परिभाषा हो सकती है जिसे पूरी तरह से योग्य नाम के साथ कहा जा सकता है।
माइकल बूर

सही - दूसरे विचार पर मुझे लगता है कि यदि सभी आभासी तरीके शुद्ध आभासी थे तो कंपाइलर विएबेट को दूर कर सकता है (यह सुनिश्चित करने के लिए लिंकर को बनाने में मदद की आवश्यकता होगी कि कोई परिभाषा भी नहीं है)।
माइकल बूर

1
" उत्तर यह है कि अमूर्त वर्गों के लिए कोई भी आभासी तालिका नहीं बनाई गई है। " " इन वर्गों की कोई भी वस्तु नहीं बनाई जा सकती है, इसकी कोई आवश्यकता नहीं है! " गलत।
जिज्ञासु

मैं आपके तर्क का पालन कर सकता हूं कि किसी भी व्यवहार्य की आवश्यकता नहीं B होनी चाहिए। सिर्फ इसलिए कि इसके कुछ तरीकों में (डिफ़ॉल्ट) कार्यान्वयन का मतलब यह नहीं है कि उन्हें एक vtable में संग्रहीत किया जाना है। लेकिन मैंने सिर्फ आपके कोड को चलाया (इसे संकलित करने के लिए कुछ सुधार किए हैं) और gcc -Sउसके बाद c++filtस्पष्ट रूप से Bइसमें शामिल करने के लिए एक व्यवहार्य है । मुझे लगता है कि हो सकता है क्योंकि vtable भी वर्ग के नाम और विरासत की तरह RTTI डेटा संग्रहीत करता है। यह एक के लिए आवश्यक हो सकता है dynamic_cast<B*>। यहां तक -fno-rttiकि व्यवहार्य दूर जाना नहीं है। इसके clang -O3बजाय gccयह अचानक चला गया है।
MvG

@MvG " सिर्फ इसलिए कि इसके कुछ तरीकों (डिफ़ॉल्ट) कार्यान्वयन का मतलब यह नहीं है कि उन्हें एक vable में संग्रहीत किया जाना है " हाँ, इसका मतलब बस यही है।
जिज्ञासु

0

अवधारणा का बहुत प्यारा सबूत मैंने पहले ही बना दिया था (यह देखने के लिए कि क्या विरासत का मामला मायने रखता है); मुझे बताएं कि क्या C ++ का आपका कार्यान्वयन वास्तव में इसे अस्वीकार कर देता है (gcc का मेरा संस्करण केवल अनाम संरचनाओं को असाइन करने के लिए एक चेतावनी देता है, लेकिन यह एक बग है), मैं उत्सुक हूं।

CCPolite.h :

#ifndef CCPOLITE_H
#define CCPOLITE_H

/* the vtable or interface */
typedef struct {
    void (*Greet)(void *);
    void (*Thank)(void *);
} ICCPolite;

/**
 * the actual "object" literal as C++ sees it; public variables be here too 
 * all CPolite objects use(are instances of) this struct's structure.
 */
typedef struct {
    ICCPolite *vtbl;
} CPolite;

#endif /* CCPOLITE_H */

CCPolite_constructor.h :

/** 
 * unconventionally include me after defining OBJECT_NAME to automate
 * static(allocation-less) construction.
 *
 * note: I assume CPOLITE_H is included; since if I use anonymous structs
 *     for each object, they become incompatible and cause compile time errors
 *     when trying to do stuff like assign, or pass functions.
 *     this is similar to how you can't pass void * to windows functions that
 *         take handles; these handles use anonymous structs to make 
 *         HWND/HANDLE/HINSTANCE/void*/etc not automatically convertible, and
 *         require a cast.
 */
#ifndef OBJECT_NAME
    #error CCPolite> constructor requires object name.
#endif

CPolite OBJECT_NAME = {
    &CCPolite_Vtbl
};

/* ensure no global scope pollution */
#undef OBJECT_NAME

main.c :

#include <stdio.h>
#include "CCPolite.h"

// | A Greeter is capable of greeting; nothing else.
struct IGreeter
{
    virtual void Greet() = 0;
};

// | A Thanker is capable of thanking; nothing else.
struct IThanker
{
    virtual void Thank() = 0;
};

// | A Polite is something that implements both IGreeter and IThanker
// | Note that order of implementation DOES MATTER.
struct IPolite1 : public IGreeter, public IThanker{};
struct IPolite2 : public IThanker, public IGreeter{};

// | implementation if IPolite1; implements IGreeter BEFORE IThanker
struct CPolite1 : public IPolite1
{
    void Greet()
    {
        puts("hello!");
    }

    void Thank()
    {
        puts("thank you!");
    }
};

// | implementation if IPolite1; implements IThanker BEFORE IGreeter
struct CPolite2 : public IPolite2
{
    void Greet()
    {
        puts("hi!");
    }

    void Thank()
    {
        puts("ty!");
    }
};

// | imposter Polite's Greet implementation.
static void CCPolite_Greet(void *)
{
    puts("HI I AM C!!!!");
}

// | imposter Polite's Thank implementation.
static void CCPolite_Thank(void *)
{
    puts("THANK YOU, I AM C!!");
}

// | vtable of the imposter Polite.
ICCPolite CCPolite_Vtbl = {
    CCPolite_Thank,
    CCPolite_Greet    
};

CPolite CCPoliteObj = {
    &CCPolite_Vtbl
};

int main(int argc, char **argv)
{
    puts("\npart 1");
    CPolite1 o1;
    o1.Greet();
    o1.Thank();

    puts("\npart 2");    
    CPolite2 o2;    
    o2.Greet();
    o2.Thank();    

    puts("\npart 3");    
    CPolite1 *not1 = (CPolite1 *)&o2;
    CPolite2 *not2 = (CPolite2 *)&o1;
    not1->Greet();
    not1->Thank();
    not2->Greet();
    not2->Thank();

    puts("\npart 4");        
    CPolite1 *fake = (CPolite1 *)&CCPoliteObj;
    fake->Thank();
    fake->Greet();

    puts("\npart 5");        
    CPolite2 *fake2 = (CPolite2 *)fake;
    fake2->Thank();
    fake2->Greet();

    puts("\npart 6");        
    #define OBJECT_NAME fake3
    #include "CCPolite_constructor.h"
    fake = (CPolite1 *)&fake3;
    fake->Thank();
    fake->Greet();

    puts("\npart 7");        
    #define OBJECT_NAME fake4
    #include "CCPolite_constructor.h"
    fake2 = (CPolite2 *)&fake4;
    fake2->Thank();
    fake2->Greet();    

    return 0;
}

उत्पादन:

part 1
hello!
thank you!

part 2
hi!
ty!

part 3
ty!
hi!
thank you!
hello!

part 4
HI I AM C!!!!
THANK YOU, I AM C!!

part 5
THANK YOU, I AM C!!
HI I AM C!!!!

part 6
HI I AM C!!!!
THANK YOU, I AM C!!

part 7
THANK YOU, I AM C!!
HI I AM C!!!!

ध्यान दें कि मैं कभी भी अपनी नकली वस्तु को आवंटित नहीं कर रहा हूं, कोई विनाश करने की आवश्यकता नहीं है; विध्वंसक को स्वचालित रूप से गतिशील रूप से आवंटित वस्तुओं के दायरे के अंत में डाल दिया जाता है ताकि वस्तु शाब्दिक और विटेबल कोटर की स्मृति को पुनः प्राप्त किया जा सके।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.