विरासत
वंशानुक्रम का पूरा बिंदु कई अलग-अलग कार्यान्वयनों के बीच एक सामान्य इंटरफ़ेस और प्रोटोकॉल साझा करना है, ताकि किसी व्युत्पन्न वर्ग के उदाहरण को किसी भी अन्य व्युत्पन्न प्रकार से किसी अन्य उदाहरण के लिए पहचान लिया जा सके।
C ++ वंशानुक्रम में भी इसे लागू करने के विवरण के साथ लाता है, अंकन (या अंकन नहीं) विध्वंसक के रूप में आभासी एक ऐसा कार्यान्वयन विस्तार है।
बांधने का कार्य
अब जब एक फंक्शन, या इसके किसी विशेष केस जैसे कि कंस्ट्रक्टर या डिस्ट्रक्टर को बुलाया जाता है, तो कंपाइलर को यह चुनना चाहिए कि कौन सा फंक्शन कार्यान्वयन था। फिर उसे मशीन कोड जनरेट करना होगा जो इस आशय का अनुसरण करता है।
इसे काम करने का सबसे सरल तरीका यह होगा कि आप संकलन समय पर फ़ंक्शन का चयन करें और बस पर्याप्त मशीन कोड का उत्सर्जन करें ताकि किसी भी मूल्य की परवाह किए बिना, जब कोड का वह टुकड़ा निष्पादित हो जाए, तो यह हमेशा फ़ंक्शन के लिए कोड चलाता है। यह वंशानुक्रम को छोड़कर महान काम करता है।
यदि हमारे पास एक फ़ंक्शन के साथ बेस क्लास है (कंस्ट्रक्टर या डिस्ट्रक्टर सहित कोई भी फ़ंक्शन हो सकता है) और आपका कोड इस पर एक फ़ंक्शन को कॉल करता है, तो इसका क्या मतलब है?
अपने उदाहरण से लेते हुए, यदि आपने initialize_vector()
कंपाइलर को यह तय करना है कि क्या आप वास्तव में पाए गए कार्यान्वयन को कॉल करने के लिए हैं Base
, या कार्यान्वयन पाया गया है Derived
। इसे तय करने के दो तरीके हैं:
- पहला यह तय करना है कि क्योंकि आप एक
Base
प्रकार से बुलाए गए हैं , इसलिए आपका मतलब कार्यान्वयन में है Base
।
- दूसरा यह तय करना है कि क्योंकि
Base
टाइप किए गए मूल्य में संग्रहित मूल्य का रनटाइम प्रकार हो सकता है Base
, या Derived
यह कि निर्णय किस कॉल को करना है, रनटाइम पर कॉल किया जाना चाहिए (प्रत्येक बार जब इसे कहा जाता है)।
इस बिंदु पर संकलक भ्रमित है, दोनों विकल्प समान रूप से मान्य हैं। यह virtual
मिश्रण में आता है। जब यह कीवर्ड कंपाइलर पिक ऑप्शन 2 प्रस्तुत करता है तो कोड को वास्तविक मान के साथ चलाने तक सभी संभावित कार्यान्वयनों के बीच निर्णय लेने में देरी होती है। जब यह कीवर्ड अनुपस्थित है तो कंपाइलर विकल्प 1 चुनता है क्योंकि यह अन्यथा सामान्य व्यवहार है।
कंपाइलर अभी भी वर्चुअल फ़ंक्शन कॉल के मामले में विकल्प 1 चुन सकता है। लेकिन केवल अगर यह साबित कर सकता है कि यह हमेशा मामला है।
कंस्ट्रक्टर और डिस्ट्रक्टर्स
तो हम एक वर्चुअल कंस्ट्रक्टर को क्यों नहीं निर्दिष्ट करते?
अधिक सहज कैसे संकलक के लिए निर्माता की समान कार्यान्वयन के बीच चुनेंगे Derived
और Derived2
? यह बहुत आसान है, यह नहीं हो सकता। कोई पूर्व-मौजूदा मूल्य नहीं है जिससे कंपाइलर सीख सकता है कि वास्तव में क्या इरादा था। प्री-एक्सिसिटिंग वैल्यू नहीं है क्योंकि यह कंस्ट्रक्टर का काम है।
तो हमें एक आभासी विध्वंसक को निर्दिष्ट करने की आवश्यकता क्यों है?
अधिक सहजता से संकलक कार्यान्वयन के लिए Base
और के बीच चयन कैसे करेगा Derived
? वे केवल फ़ंक्शन कॉल हैं, इसलिए फ़ंक्शन कॉल व्यवहार होता है। एक घोषित वर्चुअल डिस्ट्रक्टर के बिना कंपाइलर Base
रनटाइम प्रकार की परवाह किए बिना डिस्ट्रक्टर को सीधे बाँधने का निर्णय करेगा ।
कई संकलक में, यदि व्युत्पन्न किसी भी डेटा सदस्यों की घोषणा नहीं करता है, और न ही अन्य प्रकारों से विरासत में मिला है, तो ~Base()
वसीयत में व्यवहार उपयुक्त होगा, लेकिन इसकी गारंटी नहीं है। यह पूरी तरह से घटित होने से काम करेगा, बहुत कुछ एक फ्लेमथ्रोवर के सामने खड़ा होने जैसा कि अभी तक प्रज्वलित नहीं हुआ था। आप थोड़ी देर के लिए ठीक हैं।
C ++ में किसी भी आधार या इंटरफ़ेस प्रकार को घोषित करने का एकमात्र सही तरीका आभासी विध्वंसक घोषित करना है, ताकि सही विध्वंसक को उस प्रकार के पदानुक्रम के किसी भी उदाहरण के लिए बुलाया जाए। यह उस आवृत्ति को सही ढंग से साफ़ करने के लिए आवृत्ति के सबसे अधिक ज्ञान के साथ फ़ंक्शन की अनुमति देता है।
~derived()
वीसी के विध्वंसक को एक प्रतिनिधि प्रदान करती है । वैकल्पिक रूप से, आप मान रहे हैं किunique_ptr<base> pt
व्युत्पन्न विध्वंसक को पता चल जाएगा। एक आभासी विधि के बिना, यह मामला नहीं हो सकता। हालांकि एक यूनिक_प्रटर को एक डिलीट फंक्शन दिया जा सकता है जो कि बिना रनटाइम प्रतिनिधित्व के एक टेम्प्लेट पैरामीटर है, और यह सुविधा इस कोड के लिए किसी काम की नहीं है।