2012-01-11 16 views
30

संभव डुप्लिकेट:
Can someone explain C++ Virtual Methods?आभासी कार्यों का उपयोग क्यों करें?

मैं सी ++ आभासी कार्यों के लिए संबंधित प्रश्न है।

हम वर्चुअल फ़ंक्शंस का उपयोग कब और कब करते हैं? क्या कोई मुझे वास्तविक समय कार्यान्वयन या आभासी कार्यों का उपयोग कर सकता है?

+1

मैंने सिर्फ इस के लिए एक ब्लॉग पोस्ट बनाया है, क्योंकि मुझे एक बच्चे ने मुझसे पूछा कि "वर्चुअल फ़ंक्शंस" क्यों मुझे यह समझाना मुश्किल हो गया है? http://nrecursions.blogspot.in/2015/06/so-why-do-we-need-virtual-functions.html – Nav

उत्तर

45

जब आप बेस क्लास के लिए लागू किए गए एक के बजाय अपने व्युत्पन्न वर्ग के लिए एक निश्चित व्यवहार (पढ़ने विधि) को ओवरराइड करना चाहते हैं तो आप वर्चुअल फ़ंक्शंस का उपयोग करते हैं और आप बेस क्लास में पॉइंटर के माध्यम से रन-टाइम पर ऐसा करना चाहते हैं ।

क्लासिक उदाहरण तब होता है जब आपके पास बेस श्रेणी है जिसे Shape और ठोस आकार (कक्षाएं) से प्राप्त किया जाता है। प्रत्येक कंक्रीट क्लास ओवरराइड (वर्चुअल विधि लागू करता है) जिसे Draw() कहा जाता है।

वर्ग पदानुक्रम इस प्रकार है:

Class hierarchy

निम्नलिखित स्निपेट उदाहरण के उपयोग पता चलता है; यह Shape कक्षा पॉइंटर्स की एक सरणी बनाता है जिसमें प्रत्येक बिंदु एक विशिष्ट व्युत्पन्न क्लास ऑब्जेक्ट में होता है। रन-टाइम पर, Draw() विधि का आविष्कार परिणाम उस व्युत्पन्न वर्ग द्वारा ओवरराइड की गई विधि को कॉल करने और विशेष Shape खींचा जाता है (या प्रस्तुत किया जाता है)।

Shape *basep[] = { &line_obj, &tri_obj, 
        &rect_obj, &cir_obj}; 
for (i = 0; i < NO_PICTURES; i++) 
    basep[i] -> Draw(); 

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

उपर्युक्त SOLID डिजाइन सिद्धांतों के Open Closed Principle का एक अच्छा उदाहरण है।

+7

क्या होगा यदि हम 'आभासी' का उपयोग नहीं करते हैं और उन्हें 'रेखा' जैसे उप-वर्गों में फिर से परिभाषित करते हैं 'और 'त्रिकोण'। वहां क्या अंतर होगा? – bluejamesbond

+4

यदि फ़ंक्शन बेस क्लास में वर्चुअल नहीं था, तो आप बेस क्लास पॉइंटर के माध्यम से व्युत्पन्न कक्षा संस्करण तक नहीं पहुंच पाए। – radensb

+0

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

1

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

अनिवार्य रूप से क्या सामान्यतः "Liskov प्रतिस्थापन सिद्धांत" बारबरा लिस्कोव जो 1983

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

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

20

जानवरों की कक्षा के बारे में सोचें, और इससे प्राप्त बिल्ली, कुत्ते और गाय हैं।पशु वर्ग में

virtual void SaySomething() 
{ 
    cout << "Something"; 
} 

फ़ंक्शन।

Animal *a; 
a = new Dog(); 
a->SaySomething(); 

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

बेशक, संभावित त्रुटियों से बचने के लिए SaySomething function शुद्ध वर्चुअल होना चाहिए था।

+2

के माध्यम से व्युत्पन्न फ़ंक्शन को कॉल करना आपका उत्तर संतोषजनक था .... – haris

+0

मैं आपकी अंतिम वाक्य के बारे में काफी अनुवर्ती नहीं हूं "संभावित त्रुटियों से बचने के लिए कहें कुछ काम शुद्ध वर्चुअल होना चाहिए था।" क्या आप इसका एक उदाहरण बना सकते हैं। – user454083

+3

देर से उत्तर के लिए खेद है। इस पर विचार करें, एक डेवलपर हमारी पशु श्रेणी (कहते हैं, 'फॉक्स') विरासत में एक नई कक्षा बनाता है और SaySomething विधि को ओवरराइड करना भूल जाता है।यदि आप प्रदान की गई वर्चुअल विधि का उपयोग करते हैं, तो 'फॉक्स' उदाहरण "कुछ" कहेंगे, जो सही नहीं है। अगर हमने सत्य वर्चुअल के रूप में SaySomething घोषित किया है, तो हम 'फॉक्स' को तुरंत चालू नहीं कर पाएंगे, जिसमें 'नया फॉक्स (...)' वाला कोड एक त्रुटि उठाएगा। इस तरह, डेवलपर जिसने 'फॉक्स' वर्ग बनाया है उसे संकलन समय में उसकी गलती के बारे में अधिसूचित किया जाएगा। संकलन समय त्रुटियां अच्छी हैं क्योंकि वे समय बर्बाद नहीं करते हैं :) – holgac

20

जब आप अलग-अलग ऑब्जेक्ट्स को उसी तरह से संभालने की आवश्यकता होती है तो आप वर्चुअल फ़ंक्शंस का उपयोग करते हैं। इसे बहुरूपता कहा जाता है। मान लें कि आप कुछ आधार वर्ग है - शास्त्रीय आकार की तरह कुछ:

class Shape 
    { 
     public: 
      virtual void draw() = 0; 
      virtual ~Shape() {} 
    }; 

    class Rectange: public Shape 
    { 
     public: 
      void draw() { // draw rectangle here } 
    }; 


    class Circle: public Shape 
    { 
     public: 
      void draw() { // draw circle here } 
    }; 

अब आप अलग अलग आकार के वेक्टर हो सकता है:

vector<Shape*> shapes; 
    shapes.push_back(new Rectangle()); 
    shapes.push_back(new Circle()); 

और तुम इस तरह सभी आकृति आकर्षित कर सकते हैं:

for(vector<Shape*>::iterator i = shapes.begin(); i != shapes.end(); i++) 
    { 
      (*i)->draw(); 
    } 

इस तरह आप एक आभासी विधि - ड्रा() के साथ विभिन्न आकारों को चित्रित कर रहे हैं। विधि के उचित संस्करण को पॉइंटर के पीछे ऑब्जेक्ट के प्रकार के बारे में रन टाइम जानकारी के आधार पर चुना जाता है।

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

भी विनाशक से पहले "वर्चुअल" नोटिस करें। यदि आप पॉइंटर्स के माध्यम से ऑब्जेक्ट्स के साथ अपने बेस क्लास में ऑब्जेक्ट्स के साथ काम करने की योजना बना रहे हैं तो आपको विनाशक वर्चुअल घोषित करना चाहिए, इसलिए जब आप बेस क्लास पॉइंटर के लिए "डिलीट" कहते हैं, तो विनाशकों की सभी श्रृंखला को बुलाया जाएगा और मेमोरी लीक नहीं होंगे।