क्या एक ही वर्चुअल फंक्शन पूरी क्लास को धीमा कर देता है?
या केवल फ़ंक्शन के लिए कॉल वर्चुअल है? और क्या गति प्रभावित हो जाती है अगर आभासी फ़ंक्शन वास्तव में ओवरराइट किया गया है या नहीं, या क्या इसका कोई प्रभाव नहीं है जब तक कि यह आभासी है।
वर्चुअल फ़ंक्शंस होने से पूरी क्लास का इंफ़ॉसर धीमा पड़ जाता है क्योंकि डेटा के एक और आइटम को ऐसी क्लास के ऑब्जेक्ट से डील करते समय इनिशियलाइज़, कॉपी,… करना पड़ता है। आधा दर्जन सदस्यों के साथ एक वर्ग के लिए, अंतर नगण्य होना चाहिए। एक ऐसे वर्ग के लिए जिसमें सिर्फ एक ही 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
सही-सही टाइप किया गया हो।
Inside the C++ Object Model
द्वारा कृति पढ़ने का सुझाव देंStanley B. Lippman
। (धारा 4.2, पृष्ठ 124-131)