आभासी विध्वंसक का उपयोग करने की लागत क्या है अगर मैं इसका उपयोग करता हूं, भले ही इसकी आवश्यकता न हो?
शुरू करने की लागत किसी भी एक वर्ग के लिए आभासी समारोह (विरासत में मिला है या वर्ग परिभाषा का हिस्सा) एक संभवतः बहुत खड़ी है (या नहीं वस्तु के आधार पर) तो की तरह, वस्तु प्रति संग्रहीत एक आभासी सूचक की प्रारंभिक लागत:
struct Integer
{
virtual ~Integer() {}
int value;
};
इस मामले में, मेमोरी की लागत अपेक्षाकृत भारी है। एक वर्ग उदाहरण का वास्तविक मेमोरी आकार अब अक्सर 64-बिट आर्किटेक्चर पर इस तरह दिखाई देगा:
struct Integer
{
// 8 byte vptr overhead
int value; // 4 bytes
// typically 4 more bytes of padding for alignment of vptr
};
इस Integer
वर्ग के लिए कुल 16 बाइट्स हैं, जो कि मात्र 4 बाइट्स के विपरीत है। अगर हम इनमें से एक लाख को एक सरणी में संग्रहीत करते हैं, तो हम 16 मेगाबाइट मेमोरी उपयोग के साथ समाप्त होते हैं: दो बार सामान्य 8 एमबी एल 3 सीपीयू कैश का आकार, और इस तरह के एक सरणी के माध्यम से पुनरावृत्ति करना 4 मेगाबाइट समकक्ष की तुलना में कई गुना धीमा हो सकता है। अतिरिक्त कैश मिस और पेज दोष के परिणामस्वरूप वर्चुअल पॉइंटर के बिना।
यह वर्चुअल पॉइंटर प्रति ऑब्जेक्ट लागत, हालांकि, अधिक वर्चुअल फ़ंक्शंस के साथ नहीं बढ़ता है। आपकी कक्षा में 100 आभासी सदस्य कार्य हो सकते हैं और प्रति ओवरहेड अभी भी एक ही वर्चुअल पॉइंटर होगा।
वर्चुअल पॉइंटर आमतौर पर ओवरहेड दृष्टिकोण से अधिक तत्काल चिंता का विषय है। हालांकि, एक आभासी सूचक के अलावा प्रति उदाहरण प्रति वर्ग लागत है। वर्चुअल फ़ंक्शंस के साथ प्रत्येक वर्ग एक vtable
मेमोरी बनाता है जो उन फ़ंक्शन को संबोधित करता है जिन्हें वास्तव में कॉल करना चाहिए (वर्चुअल / डायनामिक डिस्पैच) जब वर्चुअल फ़ंक्शन कॉल किया जाता है। vptr
उदाहरण के प्रति तो संग्रहित इस वर्ग विशेष को इंगित करता है vtable
। यह ओवरहेड आमतौर पर कम चिंता का विषय है, लेकिन यह आपके द्विआधारी आकार को बढ़ा सकता है और थोड़ी सी रनटाइम लागत को जोड़ सकता है यदि इस ओवरहेड को एक जटिल कोडबेस में एक हजार वर्गों के लिए अनावश्यक रूप से भुगतान किया गया था, जैसे vtable
कि लागत का यह पक्ष वास्तव में अधिक से अधिक के लिए आनुपातिक रूप से बढ़ता है। मिक्स में अधिक वर्चुअल फ़ंक्शंस।
प्रदर्शन-गंभीर क्षेत्रों में काम करने वाले जावा डेवलपर्स इस तरह के ओवरहेड को बहुत अच्छी तरह से समझते हैं (हालांकि अक्सर बॉक्सिंग के संदर्भ में वर्णित है), चूंकि एक जावा उपयोगकर्ता द्वारा परिभाषित प्रकार एक केंद्रीय object
आधार वर्ग से विरासत में मिला है और जावा में सभी फ़ंक्शन अंतर्निहित रूप से आभासी (ओवररिएडेबल) हैं ) प्रकृति में जब तक अन्यथा चिह्नित नहीं किया गया है। नतीजतन, एक जावा Integer
इसी तरह vptr
-स्टाइल मेटाडेटा प्रति उदाहरण से जुड़े के रूप में 64-बिट प्लेटफार्मों पर 16 बाइट्स मेमोरी की आवश्यकता होती है , और जावा int
में एक रनटाइम का भुगतान किए बिना किसी वर्ग में एकल की तरह लपेटना आमतौर पर असंभव है। इसके लिए प्रदर्शन लागत।
तो सवाल यह है: क्यों सी + + डिफ़ॉल्ट रूप से सभी विध्वंसक आभासी सेट नहीं करता है?
C ++ वास्तव में एक "वेतन के रूप में आप जाते हैं" मानसिकता के साथ प्रदर्शन का पक्षधर है और सी से विरासत में मिली नंगे-धातु हार्डवेयर चालित डिजाइनों का एक बहुत कुछ है। यह बेकार की पीढ़ी और गतिशील प्रेषण के लिए आवश्यक ओवरहेड को शामिल नहीं करना चाहता है हर एक वर्ग / उदाहरण शामिल है। यदि प्रदर्शन C ++ जैसी भाषा का उपयोग करने वाले प्रमुख कारणों में से एक नहीं है, तो आपको अन्य प्रोग्रामिंग भाषाओं से अधिक लाभ हो सकता है क्योंकि C ++ भाषा का बहुत कम उपयोग सुरक्षित है और इससे अधिक कठिन है आदर्श रूप से अक्सर प्रदर्शन के साथ हो सकता है इस तरह के डिजाइन के पक्ष में महत्वपूर्ण कारण।
मुझे वर्चुअल विध्वंसक का उपयोग करने की आवश्यकता नहीं है?
अक्सर। यदि किसी वर्ग को विरासत में डिज़ाइन नहीं किया गया है, तो उसे एक वर्चुअल विध्वंसक की आवश्यकता नहीं है और केवल उसी चीज़ के लिए एक बड़े ओवरहेड का भुगतान करना होगा जो इसकी आवश्यकता नहीं है। इसी तरह, भले ही एक वर्ग को विरासत में डिज़ाइन किया गया हो, लेकिन आप कभी भी आधार सूचक के माध्यम से उपप्रकार उदाहरणों को नहीं हटाते हैं, तो इसके लिए वर्चुअल विध्वंसक की भी आवश्यकता नहीं होती है। उस मामले में, एक सुरक्षित अभ्यास एक गैर-संक्रामक विध्वंसक को परिभाषित करना है, जैसे:
class BaseClass
{
protected:
// Disallow deleting/destroying subclass objects through `BaseClass*`.
~BaseClass() {}
};
किस मामले में मुझे वर्चुअल डिस्ट्रक्टर्स का उपयोग नहीं करना चाहिए?
जब आप वर्चुअल डिस्ट्रक्टर्स का उपयोग करना चाहिए तो वास्तव में इसे कवर करना आसान है। अक्सर आपके कोडबेस में बहुत अधिक कक्षाएं विरासत के लिए डिज़ाइन नहीं की जाएंगी।
std::vector
, उदाहरण के लिए, विरासत में नहीं बनाया गया है और आम तौर पर विरासत में मिला नहीं होना चाहिए (बहुत ही अस्थिर डिजाइन), जैसा कि तब इस आधार सूचक विलोपन मुद्दे के लिए प्रवण हो जाएगा ( std::vector
जानबूझकर एक आभासी विध्वंसक से बचता है) क्लॉटी ऑब्जेक्ट स्लाइसिंग मुद्दों के अलावा यदि आपका व्युत्पन्न वर्ग किसी भी नए राज्य को जोड़ता है।
सामान्य रूप से विरासत में मिला एक वर्ग या तो एक सार्वजनिक आभासी विध्वंसक या एक संरक्षित, गैर-कानूनी होना चाहिए। से C++ Coding Standards
, अध्याय ५०:
50. बेस क्लास डिस्ट्रक्टर्स को सार्वजनिक और आभासी, या संरक्षित और गैर-संवैधानिक बनाएं। हटाने के लिए, या हटाने के लिए नहीं; यह सवाल है: यदि एक बेस के लिए पॉइंटर के माध्यम से हटाने की अनुमति दी जानी चाहिए, तो बेस के विध्वंसक को सार्वजनिक और आभासी होना चाहिए। अन्यथा, यह संरक्षित और गैर-धार्मिक होना चाहिए।
C ++ की चीजों में से एक का तात्पर्य अंतर्निहित रूप से जोर देना है (क्योंकि डिजाइन वास्तव में भंगुर और अजीब होते हैं और संभवतः असुरक्षित भी होते हैं) यह विचार है कि विरासत में एक तंत्र नहीं है जिसे डिजाइन के रूप में इस्तेमाल किया जा सके। यह मन में बहुरूपता के साथ एक एक्स्टेंसिबिलिटी मैकेनिज्म है, लेकिन जिसको एक्स्टेंसिबिलिटी की जरूरत होती है, उसके लिए दूरदर्शिता की जरूरत होती है। नतीजतन, आपके आधार वर्गों को एक वंशानुगत पदानुक्रम उत्थान की जड़ों के रूप में डिज़ाइन किया जाना चाहिए, और कुछ ऐसा नहीं जिसे आप बाद में विरासत में मिला हो।
उन मामलों में जहां आप बस मौजूदा कोड का पुन: उपयोग करना चाहते हैं, रचना को अक्सर प्रोत्साहित किया जाता है (समग्र पुन: उपयोग सिद्धांत)।