हमें C ++ में आभासी कार्यों की आवश्यकता क्यों है?


1311

मैं C ++ सीख रहा हूं और मैं अभी वर्चुअल फ़ंक्शंस में शामिल हो रहा हूं।

मैंने जो कुछ पढ़ा है (पुस्तक और ऑनलाइन में) से, वर्चुअल फ़ंक्शंस बेस क्लास में फ़ंक्शंस हैं जिन्हें आप व्युत्पन्न कक्षाओं में ओवरराइड कर सकते हैं।

लेकिन पहले किताब में, जब मैं मूल विरासत के बारे में सीख रहा था, मैं बिना उपयोग किए व्युत्पन्न वर्गों में आधार कार्यों को ओवरराइड करने में सक्षम था virtual

तो मैं यहाँ क्या याद कर रहा हूँ? मुझे पता है कि वर्चुअल फ़ंक्शंस के लिए अधिक है, और यह महत्वपूर्ण प्रतीत होता है इसलिए मैं स्पष्ट होना चाहता हूं कि यह वास्तव में क्या है। मैं अभी सीधे ऑनलाइन उत्तर नहीं पा सकता हूं।


13
मैंने यहां वर्चुअल फ़ंक्शंस के लिए एक व्यावहारिक स्पष्टीकरण बनाया है: nrecursions.blogspot.in/2015/06/…
नव

4
यह शायद आभासी कार्यों का सबसे बड़ा लाभ है - आपके कोड को इस तरह से संरचना करने की क्षमता है कि नव व्युत्पन्न वर्ग स्वचालित रूप से संशोधन के बिना पुराने कोड के साथ काम करेंगे!
user3530616

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

यह अच्छा प्रश्न है। वास्तव में C ++ की यह आभासी चीज़ जावा या PHP जैसी अन्य भाषाओं में दूर हो जाती है। C ++ में आप केवल कुछ दुर्लभ मामलों के लिए थोड़ा अधिक नियंत्रण प्राप्त करते हैं (कई वंशानुक्रम या DDOD के विशेष मामले से अवगत रहें )। लेकिन इस सवाल को stackoverflow.com पर क्यों पोस्ट किया गया है?
एडगर एलोरो

मुझे लगता है कि यदि आप जल्दी बाध्यकारी-लेट बाइंडिंग और वीटेबल पर एक नज़र डालते हैं तो यह अधिक उचित होगा और समझ में आएगा। तो यहाँ एक अच्छी व्याख्या ( learncpp.com/cpp-tutorial/125-the-virtual-table ) है।
ceyun

जवाबों:


2728

यहाँ बताया गया है कि मैं समझ नहीं पाया कि क्या virtualकार्य हैं, लेकिन वे क्यों आवश्यक हैं:

मान लें कि आपके पास ये दो वर्ग हैं:

class Animal
{
    public:
        void eat() { std::cout << "I'm eating generic food."; }
};

class Cat : public Animal
{
    public:
        void eat() { std::cout << "I'm eating a rat."; }
};

अपने मुख्य समारोह में:

Animal *animal = new Animal;
Cat *cat = new Cat;

animal->eat(); // Outputs: "I'm eating generic food."
cat->eat();    // Outputs: "I'm eating a rat."

अभी तक बहुत अच्छा है, ठीक है ना? जानवर सामान्य भोजन खाते हैं, बिल्लियाँ चूहों को खाती हैं, सभी के बिना virtual

आइए इसे थोड़ा बदल दें ताकि eat()एक मध्यवर्ती फ़ंक्शन (इस उदाहरण के लिए एक तुच्छ फ़ंक्शन) के माध्यम से बुलाया जाए :

// This can go at the top of the main.cpp file
void func(Animal *xyz) { xyz->eat(); }

अब हमारा मुख्य कार्य है:

Animal *animal = new Animal;
Cat *cat = new Cat;

func(animal); // Outputs: "I'm eating generic food."
func(cat);    // Outputs: "I'm eating generic food."

उह ... हमने एक बिल्ली को पारित कर दिया func(), लेकिन यह चूहों को नहीं खाएगी। क्या आपको ओवरलोड करना चाहिए func()ताकि यह एक हो जाए Cat*? यदि आप पशु से अधिक जानवरों को प्राप्त करने के लिए है, तो वे सभी को अपनी आवश्यकता होगी func()

इसका समाधान कक्षा को एक आभासी कार्य eat()से बनाना है Animal:

class Animal
{
    public:
        virtual void eat() { std::cout << "I'm eating generic food."; }
};

class Cat : public Animal
{
    public:
        void eat() { std::cout << "I'm eating a rat."; }
};

मुख्य:

func(animal); // Outputs: "I'm eating generic food."
func(cat);    // Outputs: "I'm eating a rat."

किया हुआ।


164
इसलिए अगर मैं इसे सही तरीके से समझ रहा हूं, तो वर्चुअल सबक्लास विधि को कॉल करने की अनुमति देता है, भले ही ऑब्जेक्ट को उसके सुपरक्लास के रूप में माना जा रहा हो?
केनी वर्डेन

146
एक मध्यस्थ समारोह "फंक" के उदाहरण के माध्यम से देर से बाध्यकारी समझाने के बजाय, यहां एक अधिक सीधा प्रदर्शन है - पशु * जानवर = नया पशु; // बिल्ली * बिल्ली = नई बिल्ली; पशु * बिल्ली = नई बिल्ली; पशु> खाने (); // आउटपुट: "मैं सामान्य भोजन खा रहा हूं।" बिल्ली> खाने (); // आउटपुट: "मैं सामान्य भोजन खा रहा हूं।" भले ही आप उपवर्गित ऑब्जेक्ट (कैट) को असाइन कर रहे हों, लेकिन जिस विधि को लागू किया जा रहा है, वह पॉइंटर प्रकार (एनिमल) पर आधारित है न कि उस वस्तु के प्रकार पर, जिसके लिए वह बिंदु है। यही कारण है कि आपको "वर्चुअल" की आवश्यकता है।
रेक्सबेलिया

37
क्या मैं C ++ में इस डिफ़ॉल्ट व्यवहार को खोजने वाला केवल अजीब हूं? मुझे काम करने के लिए "वर्चुअल" के बिना कोड की उम्मीद होगी।
डेविड David वोंग

20
@ डेविड I वाँग मुझे लगता है कि virtualकुछ गतिशील बंधन बनाम स्टेटिक का परिचय देता है और हाँ अगर आप जावा जैसी भाषाओं से आ रहे हैं तो यह अजीब है।
पीटरचूला

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

671

"वर्चुअल" के बिना आपको "शुरुआती बाध्यकारी" मिलता है। जिस पद्धति का उपयोग किया जाता है, वह उस सूचक के प्रकार के आधार पर संकलित समय पर तय किया जाता है जिसे आप कॉल करते हैं।

"आभासी" के साथ आपको "देर से बाध्यकारी" मिलता है। विधि के किस कार्यान्वयन का उपयोग किया जाता है, यह इंगित किए गए ऑब्जेक्ट के प्रकार के आधार पर रन टाइम पर तय किया जाता है - मूल रूप से इसका निर्माण किस प्रकार किया गया था। यह आवश्यक नहीं है कि आप उस वस्तु को इंगित करने वाले पॉइंटर के प्रकार के आधार पर क्या सोचेंगे।

class Base
{
  public:
            void Method1 ()  {  std::cout << "Base::Method1" << std::endl;  }
    virtual void Method2 ()  {  std::cout << "Base::Method2" << std::endl;  }
};

class Derived : public Base
{
  public:
    void Method1 ()  {  std::cout << "Derived::Method1" << std::endl;  }
    void Method2 ()  {  std::cout << "Derived::Method2" << std::endl;  }
};

Base* obj = new Derived ();
  //  Note - constructed as Derived, but pointer stored as Base*

obj->Method1 ();  //  Prints "Base::Method1"
obj->Method2 ();  //  Prints "Derived::Method2"

EDIT - यह प्रश्न देखें ।

इसके अलावा - यह ट्यूटोरियल C ++ में जल्दी और देर से बाध्यकारी होता है।


11
उत्कृष्ट, और बेहतर उदाहरणों के उपयोग के साथ और जल्दी से घर जाता है। यह हालांकि, सरलीकृत है, और प्रश्नकर्ता को वास्तव में सिर्फ पृष्ठ parashift.com/c++-faq-lite/virtual-functions.html पढ़ना चाहिए । अन्य लोगों ने पहले ही इस सूत्र से जुड़े एसओ लेखों में इस संसाधन की ओर इशारा किया है, लेकिन मेरा मानना ​​है कि यह फिर से उल्लेख के लायक है।
सन्नी

36
मुझे नहीं पता कि शुरुआती और देर से बाध्यकारी सी ++ समुदाय में विशिष्ट रूप से उपयोग किए जाने वाले शब्द हैं, लेकिन सही शब्द स्थिर (संकलन के समय) और गतिशील (रनटाइम पर) बाध्यकारी हैं।
माइक

31
@ माइक - "टर्म लेट बाइंडिंग" शब्द कम से कम 1960 के दशक का है, जहां इसे एसीएम के संचार में पाया जा सकता है। । यह अच्छा नहीं होगा अगर प्रत्येक अवधारणा के लिए एक सही शब्द था? दुर्भाग्य से, यह सिर्फ इतना नहीं है। शब्द "शुरुआती बाइंडिंग" और "लेट बाइंडिंग" C ++ और यहां तक ​​कि ऑब्जेक्ट-ओरिएंटेड प्रोग्रामिंग की भविष्यवाणी करते हैं, और आपके द्वारा उपयोग की जाने वाली शर्तों के समान ही सही हैं।
स्टीव ३४

4
@BJovke - यह उत्तर C ++ 11 प्रकाशित होने से पहले लिखा गया था। फिर भी, मैंने इसे GCC 6.3.0 (डिफ़ॉल्ट रूप से C ++ 14 का उपयोग करते हुए) में बिना किसी समस्या के संकलित किया है - जाहिर है कि चर घोषणा और mainफ़ंक्शन में कॉल आदि को लपेटकर पॉइंटर-टू-व्युत्पन्न रूप से इंगित करने के लिए पॉइंटर टू बेस (अधिक सामान्य रूप से विशिष्ट विशेष रूप से डाली जाती हैं)। वीज़ा-वर्सा के लिए आपको एक स्पष्ट कलाकार की आवश्यकता होती है, आमतौर पर ए dynamic_cast। और कुछ भी - अपरिभाषित व्यवहार के लिए बहुत संभावना है ताकि आप यह जान सकें कि आप क्या कर रहे हैं। मेरी जानकारी के अनुसार, यह C ++ 98 से पहले भी नहीं बदला है।
स्टीव 314

10
ध्यान दें कि आज C ++ कंपाइलर अक्सर देर से जल्दी बाध्यकारी में अनुकूलन कर सकते हैं - जब वे निश्चित हो सकते हैं कि बंधन क्या होने जा रहा है। इसे "डी-वर्चुअलाइजेशन" के रूप में भी जाना जाता है।
einpoklum

83

आपको इसे प्रदर्शित करने के लिए कम से कम 1 स्तर की विरासत और एक डाउनकास्ट की आवश्यकता है। यहाँ एक बहुत ही सरल उदाहरण दिया गया है:

class Animal
{        
    public: 
      // turn the following virtual modifier on/off to see what happens
      //virtual   
      std::string Says() { return "?"; }  
};

class Dog: public Animal
{
    public: std::string Says() { return "Woof"; }
};

void test()
{
    Dog* d = new Dog();
    Animal* a = d;       // refer to Dog instance with Animal pointer

    std::cout << d->Says();   // always Woof
    std::cout << a->Says();   // Woof or ?, depends on virtual
}

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

7
: वर्चुअल कीवर्ड के साथ Woof । वर्चुअल कीवर्ड के बिना: ?
हेशम एरावकी

@HeshamEraqi वर्चुअल के बिना यह जल्दी बाध्यकारी है और यह दिखाएगा "?" आधार वर्ग की
अहमद

46

आपको सुरक्षित डाउनकास्टिंग , सरलता और संक्षिप्तता के लिए आभासी तरीकों की आवश्यकता है ।

यह है कि आभासी तरीके क्या करते हैं: वे स्पष्ट रूप से सरल और संक्षिप्त कोड के साथ सुरक्षित रूप से डाउनकास्ट करते हैं, और अधिक जटिल और क्रिया कोड में असुरक्षित मैनुअल कास्ट से बचते हैं जो आपके पास अन्यथा होगा।


गैर-आभासी विधि ⇒ स्थिर बंधन

निम्न कोड जानबूझकर "गलत" है। यह valueविधि की घोषणा नहीं करता है virtual, और इसलिए एक अनपेक्षित "गलत" परिणाम उत्पन्न करता है, जिसका नाम है 0:

#include <iostream>
using namespace std;

class Expression
{
public:
    auto value() const
        -> double
    { return 0.0; }         // This should never be invoked, really.
};

class Number
    : public Expression
{
private:
    double  number_;

public:
    auto value() const
        -> double
    { return number_; }     // This is OK.

    Number( double const number )
        : Expression()
        , number_( number )
    {}
};

class Sum
    : public Expression
{
private:
    Expression const*   a_;
    Expression const*   b_;

public:
    auto value() const
        -> double
    { return a_->value() + b_->value(); }       // Uhm, bad! Very bad!

    Sum( Expression const* const a, Expression const* const b )
        : Expression()
        , a_( a )
        , b_( b )
    {}
};

auto main() -> int
{
    Number const    a( 3.14 );
    Number const    b( 2.72 );
    Number const    c( 1.0 );

    Sum const       sum_ab( &a, &b );
    Sum const       sum( &sum_ab, &c );

    cout << sum.value() << endl;
}

लाइन में "खराब" Expression::valueविधि के रूप में टिप्पणी की जाती है, क्योंकि सांख्यिकीय रूप से ज्ञात प्रकार (संकलन के समय ज्ञात प्रकार) है Expression, और valueविधि आभासी नहीं है।


आभासी विधि ⇒ गतिशील बंधन।

वैधानिक valueरूप virtualसे ज्ञात प्रकार में घोषित करना Expressionयह सुनिश्चित करता है कि प्रत्येक कॉल यह जांच करेगी कि यह किस प्रकार की वास्तविक वस्तु है, और valueउस गतिशील प्रकार के लिए प्रासंगिक कार्यान्वयन को कॉल करें :

#include <iostream>
using namespace std;

class Expression
{
public:
    virtual
    auto value() const -> double
        = 0;
};

class Number
    : public Expression
{
private:
    double  number_;

public:
    auto value() const -> double
        override
    { return number_; }

    Number( double const number )
        : Expression()
        , number_( number )
    {}
};

class Sum
    : public Expression
{
private:
    Expression const*   a_;
    Expression const*   b_;

public:
    auto value() const -> double
        override
    { return a_->value() + b_->value(); }    // Dynamic binding, OK!

    Sum( Expression const* const a, Expression const* const b )
        : Expression()
        , a_( a )
        , b_( b )
    {}
};

auto main() -> int
{
    Number const    a( 3.14 );
    Number const    b( 2.72 );
    Number const    c( 1.0 );

    Sum const       sum_ab( &a, &b );
    Sum const       sum( &sum_ab, &c );

    cout << sum.value() << endl;
}

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

प्रासंगिक कार्यान्वयन सबसे विशिष्ट (सबसे व्युत्पन्न) वर्ग में से एक है।

ध्यान दें कि यहां व्युत्पन्न वर्गों में विधि कार्यान्वयन चिह्नित नहीं हैं virtual, बल्कि इसके बजाय चिह्नित हैं override। उन्हें चिह्नित किया जा सकता है virtualलेकिन वे स्वचालित रूप से आभासी हैं। overrideकीवर्ड सुनिश्चित करें कि यदि कोई है नहीं कुछ आधार वर्ग में इस तरह के एक आभासी विधि है, तो आप एक त्रुटि (जो वांछनीय है) प्राप्त होंगे।


आभासी तरीकों के बिना ऐसा करने की कुरूपता

virtualएक के बिना गतिशील बंधन के कुछ Do It Yourself संस्करण को लागू करना होगा । यह ऐसा है जिसमें आम तौर पर असुरक्षित मैनुअल डाउनकास्टिंग, जटिलता और वाचालता शामिल होती है।

एकल फ़ंक्शन के मामले के लिए, जैसा कि यहाँ है, यह ऑब्जेक्ट में एक फ़ंक्शन पॉइंटर को स्टोर करने और उस फ़ंक्शन पॉइंटर के माध्यम से कॉल करने के लिए पर्याप्त है, लेकिन यहां तक ​​कि इसमें कुछ असुरक्षित डाउनकास्ट, जटिलता और वर्बोसिटी शामिल हैं, बुद्धि के लिए:

#include <iostream>
using namespace std;

class Expression
{
protected:
    typedef auto Value_func( Expression const* ) -> double;

    Value_func* value_func_;

public:
    auto value() const
        -> double
    { return value_func_( this ); }

    Expression(): value_func_( nullptr ) {}     // Like a pure virtual.
};

class Number
    : public Expression
{
private:
    double  number_;

    static
    auto specific_value_func( Expression const* expr )
        -> double
    { return static_cast<Number const*>( expr )->number_; }

public:
    Number( double const number )
        : Expression()
        , number_( number )
    { value_func_ = &Number::specific_value_func; }
};

class Sum
    : public Expression
{
private:
    Expression const*   a_;
    Expression const*   b_;

    static
    auto specific_value_func( Expression const* expr )
        -> double
    {
        auto const p_self  = static_cast<Sum const*>( expr );
        return p_self->a_->value() + p_self->b_->value();
    }

public:
    Sum( Expression const* const a, Expression const* const b )
        : Expression()
        , a_( a )
        , b_( b )
    { value_func_ = &Sum::specific_value_func; }
};


auto main() -> int
{
    Number const    a( 3.14 );
    Number const    b( 2.72 );
    Number const    c( 1.0 );

    Sum const       sum_ab( &a, &b );
    Sum const       sum( &sum_ab, &c );

    cout << sum.value() << endl;
}

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


40

वर्चुअल फ़ंक्शंस का उपयोग रनटाइम पॉलीमॉर्फिज़्म का समर्थन करने के लिए किया जाता है ।

यही है, वर्चुअल कीवर्ड कंपाइलर से कहता है कि वह कंपाइलिंग टाइम पर निर्णय (फंक्शन बाइंडिंग का) न करे, बल्कि उसे रिपेयर के लिए स्थगित कर दे

  • आप कीवर्ड virtualको इसके बेस क्लास डिक्लेरेशन में शामिल करके एक फंक्शन को वर्चुअल बना सकते हैं । उदाहरण के लिए,

     class Base
     {
        virtual void func();
     }
  • एक जब बेस कक्षा एक आभासी सदस्य कार्य है, किसी भी वर्ग बेस कक्षा से विरासत में मिली सकता है कि फिर से परिभाषित साथ समारोह ठीक उसी प्रोटोटाइप यानी केवल कार्यक्षमता, नए सिरे से परिभाषित किया जा सकता है समारोह की नहीं इंटरफ़ेस।

     class Derive : public Base
     {
        void func();
     }
  • बेस क्लास पॉइंटर का उपयोग बेस क्लास ऑब्जेक्ट के साथ-साथ एक व्युत्पन्न क्लास ऑब्जेक्ट को इंगित करने के लिए किया जा सकता है।

  • जब बेस क्लास पॉइंटर का उपयोग करके वर्चुअल फ़ंक्शन को कॉल किया जाता है, तो कंपाइलर रन-टाइम पर निर्णय लेता है कि फ़ंक्शन का कौन सा संस्करण - यानी बेस क्लास संस्करण या ओवरराइड डिराइव्ड क्लास संस्करण - कहा जाना है। इसे Runtime Polymorphism कहा जाता है ।

34

यदि आधार वर्ग है Base, और एक व्युत्पन्न वर्ग है Der, तो आपके पास एक Base *pसंकेतक हो सकता है जो वास्तव में एक उदाहरण के लिए इंगित करता है Der। जब आप फोन p->foo();, अगर fooहै नहीं आभासी है, तो Baseइसके बारे में के संस्करण कार्यान्वित करता है, कि इस तथ्य की अनदेखी कर pवास्तव में एक करने के लिए अंक Der। यदि foo है आभासी, p->foo()की "leafmost" ओवरराइड कार्यान्वित foo, पूरी तरह से ध्यान में उठाई-जाने वाले आइटम का वास्तविक कक्षा लेने। तो आभासी और गैर-आभासी के बीच का अंतर वास्तव में बहुत महत्वपूर्ण है: पूर्व रनटाइम बहुरूपता , OO प्रोग्रामिंग की मूल अवधारणा की अनुमति देता है , जबकि बाद वाला नहीं करता है।


8
मुझे आपके विरोधाभास से नफरत है, लेकिन संकलन-समय के बहुरूपता अभी भी बहुरूपता है। यहां तक ​​कि गैर-सदस्य कार्यों को ओवरलोड करना भी बहुरूपता का एक रूप है - आपके लिंक में शब्दावली का उपयोग करते हुए तदर्थ बहुरूपता। यहाँ अंतर प्रारंभिक और देर से बाध्यकारी के बीच है।
स्टीव 314

7
@ स्टीव ३१४, आप पांडित्यपूर्ण रूप से सही हैं (एक साथी पेडेंट के रूप में, मैं इसे स्वीकार करता हूं ;-) - लापता विशेषण को जोड़ने के लिए उत्तर संपादित करना ;-)।
एलेक्स मार्टेली 16

26

वर्चुअल फंक्शन की आवश्यकता बताई गई [समझने में आसान]

#include<iostream>

using namespace std;

class A{
public: 
        void show(){
        cout << " Hello from Class A";
    }
};

class B :public A{
public:
     void show(){
        cout << " Hello from Class B";
    }
};


int main(){

    A *a1 = new B; // Create a base class pointer and assign address of derived object.
    a1->show();

}

आउटपुट होगा:

Hello from Class A.

लेकिन आभासी समारोह के साथ:

#include<iostream>

using namespace std;

class A{
public:
    virtual void show(){
        cout << " Hello from Class A";
    }
};

class B :public A{
public:
    virtual void show(){
        cout << " Hello from Class B";
    }
};


int main(){

    A *a1 = new B;
    a1->show();

}

आउटपुट होगा:

Hello from Class B.

इसलिए वर्चुअल फ़ंक्शन के साथ आप रनटाइम पॉलीमोर्फिज़्म प्राप्त कर सकते हैं।


25

मैं वर्चुअल फ़ंक्शन का एक और उपयोग जोड़ना चाहूंगा, हालांकि यह उपरोक्त अवधारणाओं के समान अवधारणा का उपयोग करता है, लेकिन मुझे लगता है कि इसके उल्लेख के लायक है।

विरतुराल

बेस क्लास डिस्ट्रॉक्टर को आभासी घोषित किए बिना, नीचे इस कार्यक्रम पर विचार करें; बिल्ली के लिए स्मृति को साफ नहीं किया जा सकता है।

class Animal {
    public:
    ~Animal() {
        cout << "Deleting an Animal" << endl;
    }
};
class Cat:public Animal {
    public:
    ~Cat() {
        cout << "Deleting an Animal name Cat" << endl;
    }
};

int main() {
    Animal *a = new Cat();
    delete a;
    return 0;
}

आउटपुट:

Deleting an Animal
class Animal {
    public:
    virtual ~Animal() {
        cout << "Deleting an Animal" << endl;
    }
};
class Cat:public Animal {
    public:
    ~Cat(){
        cout << "Deleting an Animal name Cat" << endl;
    }
};

int main() {
    Animal *a = new Cat();
    delete a;
    return 0;
}

आउटपुट:

Deleting an Animal name Cat
Deleting an Animal

11
without declaring Base class destructor as virtual; memory for Cat may not be cleaned up.यह उससे भी बुरा है। आधार सूचक / संदर्भ के माध्यम से किसी व्युत्पन्न वस्तु को हटाना शुद्ध अपरिभाषित व्यवहार है। तो, यह सिर्फ इतना नहीं है कि कुछ मेमोरी लीक हो सकती है। इसके बजाय, कार्यक्रम बीमार है, इसलिए कंपाइलर इसे किसी भी चीज़ में बदल सकता है: मशीन कोड जो ठीक काम करने के लिए होता है, या कुछ भी नहीं करता है, या राक्षसों को आपकी नाक से बुलाता है, या यही कारण है कि, अगर कोई प्रोग्राम ऐसे में डिज़ाइन किया गया है। एक तरीका है कि कुछ उपयोगकर्ता आधार संदर्भ के माध्यम से एक व्युत्पन्न उदाहरण को हटा सकते हैं , आधार में एक वर्चुअल विध्वंसक होना चाहिए
अंडरस्कोर_ड

21

आपको ओवरराइडिंग और ओवरलोडिंग के बीच अंतर करना होगा। virtualकीवर्ड के बिना आप केवल आधार वर्ग की एक विधि को अधिभारित करते हैं। इसका मतलब छिपाने के अलावा कुछ नहीं है। मान लीजिए कि आपके पास एक आधार वर्ग Baseऔर एक व्युत्पन्न वर्ग है Specializedजो दोनों को लागू करते हैं void foo()। अब आपके पास एक संकेतक है Base, जिसका एक उदाहरण इंगित करने के लिए है Specialized। जब आप foo()उस पर कॉल करते हैं तो आप उस अंतर का निरीक्षण कर सकते हैं जो virtualबनाता है: यदि विधि आभासी है, तो कार्यान्वयन का Specializedउपयोग किया जाएगा, यदि यह गायब है, तो से संस्करण Baseचुना जाएगा। आधार वर्ग से तरीकों को अधिभार नहीं देना सबसे अच्छा अभ्यास है। एक विधि को गैर-आभासी बनाना इसके लेखक का तरीका है जो आपको बताता है कि उपवर्गों में इसका विस्तार अभीष्ट नहीं है।


3
virtualतुम्हारे बिना अतिभार नहीं है। आप छाया कर रहे हैं । यदि एक बेस क्लास Bमें एक या अधिक कार्य होते हैं foo, और व्युत्पन्न वर्ग Dएक fooनाम को परिभाषित करता है , जो उन सभी -s को foo छुपाताfoo है B। वे B::fooगुंजाइश रिज़ॉल्यूशन का उपयोग करने के रूप में पहुंच जाते हैं । ओवरलोडिंग के लिए B::fooकार्यों को बढ़ावा देने के Dलिए, आपको उपयोग करना होगा using B::foo
काज

20

C ++ में हमें Virtual Methods की आवश्यकता क्यों है?

शीघ्र जवाब:

  1. यह हमें ऑब्जेक्ट ओरिएंटेड प्रोग्रामिंग के लिए आवश्यक "सामग्री" 1 में से एक प्रदान करता है ।

Bjarne स्ट्रॉस्ट्रुप C ++ प्रोग्रामिंग में: सिद्धांत और अभ्यास, (14.3):

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

  1. यदि आपको वर्चुअल फ़ंक्शन कॉल 2 की आवश्यकता है तो यह सबसे तेज़ अधिक कुशल कार्यान्वयन है ।

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


1. विरासत, रन-टाइम बहुरूपता का उपयोग और एनकैप्सुलेशन ऑब्जेक्ट-ओरिएंटेड प्रोग्रामिंग की सबसे आम परिभाषा है ।

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

3. कुछ यह बताने के लिए कि वर्चुअल फंक्शन वाले बेस क्लास को कॉल करने पर कौन सा फंक्शन वास्तव में आ जाता है।


15

मैंने एक बेहतर पढ़ने के लिए एक वार्तालाप के रूप में अपना उत्तर दिया है:


हमें आभासी कार्यों की आवश्यकता क्यों है?

बहुरूपता के कारण।

बहुरूपता क्या है?

तथ्य यह है कि एक बेस पॉइंटर भी व्युत्पन्न प्रकार की वस्तुओं को इंगित कर सकता है।

बहुरूपता की यह परिभाषा आभासी कार्यों की आवश्यकता में कैसे नेतृत्व करती है?

खैर, जल्दी बंधन के माध्यम से

जल्दी बाध्यकारी क्या है?

C ++ में अर्ली बाइंडिंग (कंपाइल-टाइम बाइंडिंग) का मतलब है कि प्रोग्राम को निष्पादित करने से पहले एक फ़ंक्शन कॉल को ठीक किया गया है।

इसलिए...?

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

यदि यह नहीं है कि हम क्या करना चाहते हैं, तो इसकी अनुमति क्यों है?

क्योंकि हमें बहुरूपता चाहिए!

फिर बहुरूपता का क्या फायदा?

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

मुझे अभी भी नहीं पता है कि वर्चुअल फ़ंक्शंस कितने अच्छे हैं ...! और यह मेरा पहला सवाल था!

ठीक है, यह इसलिए है क्योंकि आपने अपना प्रश्न भी जल्द ही पूछ लिया है!

हमें आभासी कार्यों की आवश्यकता क्यों है?

मान लें कि आपने आधार सूचक के साथ एक फ़ंक्शन कहा, जिसमें उसके व्युत्पन्न वर्गों में से किसी एक वस्तु का पता था। जैसा कि हम इसके बारे में ऊपर बात कर चुके हैं, रन-टाइम में, यह पॉइंटर डीफ़र हो जाता है, अब तक बहुत अच्छा है, हालाँकि, हम एक विधि (== एक सदस्य फ़ंक्शन) "हमारे व्युत्पन्न वर्ग" से निष्पादित होने की उम्मीद करते हैं! हालांकि, एक ही विधि (एक ही हैडर है) पहले से ही आधार वर्ग में परिभाषित किया गया है, इसलिए आपके कार्यक्रम को अन्य विधि चुनने के लिए क्यों परेशान होना चाहिए? दूसरे शब्दों में, मेरा मतलब है कि आप इस परिदृश्य को कैसे बता सकते हैं, जिसे हम सामान्य रूप से पहले देखते थे।

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

एक अलग कार्यान्वयन क्यों?

आप ठक-ठक! एक अच्छी किताब पढ़ो !

ठीक है, प्रतीक्षा प्रतीक्षा प्रतीक्षा करें, कोई व्यक्ति आधार बिंदु का उपयोग करने के लिए परेशान क्यों होगा, जब वह / वह केवल व्युत्पन्न प्रकार के संकेत का उपयोग कर सकता है? आप जज हैं, क्या यह सब इसके लायक है? इन दो स्निपेट्स को देखें:

// 1:

Parent* p1 = &boy;
p1 -> task();
Parent* p2 = &girl;
p2 -> task();

// 2:

Boy* p1 = &boy;
p1 -> task();
Girl* p2 = &girl;
p2 -> task();

ठीक है, हालांकि मुझे लगता है कि 1 अभी भी 2 से बेहतर है , आप 1 लिख सकते हैं को इस तरह हैं:

// 1:

Parent* p1 = &boy;
p1 -> task();
p1 = &girl;
p1 -> task();

और इसके अलावा, आपको पता होना चाहिए कि यह अभी तक आपके द्वारा बताई गई सभी चीजों का केवल एक आकस्मिक उपयोग है। इसके बजाय, उदाहरण के लिए मान लें कि आपके प्रोग्राम में एक ऐसा फंक्शन था जिसमें क्रमशः व्युत्पन्न वर्गों में से प्रत्येक से तरीकों का उपयोग किया गया था (getMonthBenefit ()):

double totalMonthBenefit = 0;    
std::vector<CentralShop*> mainShop = { &shop1, &shop2, &shop3, &shop4, &shop5, &shop6};
for(CentralShop* x : mainShop){
     totalMonthBenefit += x -> getMonthBenefit();
}

अब, बिना किसी सिरदर्द के इसे फिर से लिखने का प्रयास करें !

double totalMonthBenefit=0;
Shop1* branch1 = &shop1;
Shop2* branch2 = &shop2;
Shop3* branch3 = &shop3;
Shop4* branch4 = &shop4;
Shop5* branch5 = &shop5;
Shop6* branch6 = &shop6;
totalMonthBenefit += branch1 -> getMonthBenefit();
totalMonthBenefit += branch2 -> getMonthBenefit();
totalMonthBenefit += branch3 -> getMonthBenefit();
totalMonthBenefit += branch4 -> getMonthBenefit();
totalMonthBenefit += branch5 -> getMonthBenefit();
totalMonthBenefit += branch6 -> getMonthBenefit();

और वास्तव में, यह अभी तक एक आकस्मिक उदाहरण हो सकता है!


2
एक (सुपर-) ऑब्जेक्ट प्रकार का उपयोग करके विभिन्न प्रकार के (उप-) ऑब्जेक्ट्स पर पुनरावृति की अवधारणा को हाइलाइट किया जाना चाहिए, यह एक अच्छा बिंदु है जो आपने दिया, धन्यवाद
harshvchawla

14

जब आपके पास बेस क्लास में कोई फ़ंक्शन होता है, तो आप व्युत्पन्न वर्ग में कर सकते हैं Redefineया कर सकते हैं Override

एक विधि को फिर से परिभाषित करना : व्युत्पन्न वर्ग में आधार वर्ग की विधि के लिए एक नया कार्यान्वयन दिया गया है। सुविधा नहीं हैDynamic binding

एक विधि अधिभावी : Redefiningएकvirtual methodव्युत्पन्न वर्ग में आधार वर्ग की। वर्चुअल विधि डायनामिक बाइंडिंग की सुविधा देती है

तो जब आपने कहा:

लेकिन पहले किताब में, जब मैं मूल विरासत के बारे में सीख रहा था, तो मैं 'वर्चुअल' का उपयोग किए बिना व्युत्पन्न कक्षाओं में आधार विधियों को ओवरराइड करने में सक्षम था।

आप इसे ओवरराइड नहीं कर रहे थे क्योंकि बेस क्लास में विधि आभासी नहीं थी, बल्कि आप इसे फिर से परिभाषित कर रहे थे


11

यदि आप अंतर्निहित तंत्र को जानते हैं तो यह मदद करता है। C ++ C प्रोग्रामर द्वारा उपयोग की जाने वाली कुछ कोडिंग तकनीकों को औपचारिक रूप देता है, "वर्ग" को "ओवरले" का उपयोग करके प्रतिस्थापित किया जाता है - सामान्य हेडर सेक्शन वाले स्ट्रक्चर्स का उपयोग विभिन्न प्रकार की वस्तुओं को संभालने के लिए किया जाएगा लेकिन कुछ सामान्य डेटा या संचालन के साथ। आम तौर पर ओवरले (सामान्य भाग) की आधार संरचना में एक फ़ंक्शन तालिका के लिए एक संकेतक होता है जो प्रत्येक ऑब्जेक्ट प्रकार के लिए रूटीन के एक अलग सेट की ओर इशारा करता है। C ++ एक ही काम करता है लेकिन तंत्र को छुपाता है यानी C ++ ptr->func(...)जहां सी के रूप में वर्चुअल वर्चुअल है(*ptr->func_table[func_num])(ptr,...) फ़र्क , जहाँ व्युत्पन्न वर्गों के बीच क्या परिवर्तन होता है func_table content है। [एक गैर-आभासी तरीका ptr-> func () सिर्फ mangled_func (ptr, ..) में अनुवाद करता है।]]

उस का उत्थान यह है कि आपको केवल व्युत्पन्न वर्ग के तरीकों को कॉल करने के लिए बेस क्लास को समझने की आवश्यकता है, अर्थात यदि कोई रूट क्लास A को समझता है, तो आप इसे एक व्युत्पन्न वर्ग B पॉइंटर पास कर सकते हैं, तब कहे जाने वाले वर्चुअल तरीके होंगे B के बजाय A के बाद से आप फंक्शन टेबल B पॉइंट पर जाते हैं।


8

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

इस लिंक में अधिक जानकारी http://cplusplusinterviews.blogspot.sg/2015/04/virtual-mechanism.html


7

आभासी कीवर्ड बलों संकलक विधि कार्यान्वयन में परिभाषित लेने के लिए वस्तु के वर्ग के बजाय में सूचक के वर्ग।

Shape *shape = new Triangle(); 
cout << shape->getName();

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

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

अंत में, C ++ में वर्चुअल की आवश्यकता क्यों है, इसे जावा की तरह डिफ़ॉल्ट व्यवहार क्यों नहीं किया जाता है?

  1. सी ++ "जीरो ओवरहेड" और "आप जो उपयोग करते हैं उसके लिए भुगतान करें" के सिद्धांतों पर आधारित है। इसलिए यह आपके लिए गतिशील प्रेषण करने की कोशिश नहीं करता है, जब तक कि आपको इसकी आवश्यकता न हो।
  2. इंटरफ़ेस को अधिक नियंत्रण प्रदान करने के लिए। एक फ़ंक्शन को गैर-आभासी बनाने से, इंटरफ़ेस / अमूर्त वर्ग अपने सभी कार्यान्वयनों में व्यवहार को नियंत्रित कर सकता है।

4

हमें आभासी कार्यों की आवश्यकता क्यों है?

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

आभासी कार्यों के महत्व को समझने के लिए दो सरल कार्यक्रमों की तुलना करें:

आभासी कार्यों के बिना कार्यक्रम:

#include <iostream>
using namespace std;

class father
{
    public: void get_age() {cout << "Fathers age is 50 years" << endl;}
};

class son: public father
{
    public : void get_age() { cout << "son`s age is 26 years" << endl;}
};

int main(){
    father *p_father = new father;
    son *p_son = new son;

    p_father->get_age();
    p_father = p_son;
    p_father->get_age();
    p_son->get_age();
    return 0;
}

उत्पादन:

Fathers age is 50 years
Fathers age is 50 years
son`s age is 26 years

आभासी समारोह के साथ कार्यक्रम:

#include <iostream>
using namespace std;

class father
{
    public:
        virtual void get_age() {cout << "Fathers age is 50 years" << endl;}
};

class son: public father
{
    public : void get_age() { cout << "son`s age is 26 years" << endl;}
};

int main(){
    father *p_father = new father;
    son *p_son = new son;

    p_father->get_age();
    p_father = p_son;
    p_father->get_age();
    p_son->get_age();
    return 0;
}

उत्पादन:

Fathers age is 50 years
son`s age is 26 years
son`s age is 26 years

दोनों आउटपुटों का बारीकी से विश्लेषण करके कोई भी वर्चुअल फ़ंक्शंस के महत्व को समझ सकता है।


3

OOP उत्तर: उप-प्रकार बहुरूपता

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

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

नोट: उपप्रकार का अर्थ है आधार वर्ग, और उपप्रकार का अर्थ है विरासत में मिला वर्ग।

उप-प्रकार बहुरूपता के बारे में आगे पढ़ने

तकनीकी उत्तर: डायनेमिक डिस्पैच

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

इसके अलावा C ++ और डायनामिक डिस्पैच में पॉलिमॉर्फिज्म पढ़ना

कार्यान्वयन उत्तर: व्यवहार्य प्रविष्टि बनाता है

तरीकों पर प्रत्येक संशोधक "आभासी" के लिए, C ++ कंपाइलर आमतौर पर उस कक्षा के व्यवहार्य में एक प्रविष्टि बनाते हैं जिसमें विधि घोषित की जाती है। यह कैसे सामान्य सी ++ संकलक गतिशील डिस्पैच का एहसास करता है ।

आगे पढ़ने vtables


उदाहरण कोड

#include <iostream>

using namespace std;

class Animal {
public:
    virtual void MakeTypicalNoise() = 0; // no implementation needed, for abstract classes
    virtual ~Animal(){};
};

class Cat : public Animal {
public:
    virtual void MakeTypicalNoise()
    {
        cout << "Meow!" << endl;
    }
};

class Dog : public Animal {
public:
    virtual void MakeTypicalNoise() { // needs to be virtual, if subtype polymorphism is also needed for Dogs
        cout << "Woof!" << endl;
    }
};

class Doberman : public Dog {
public:
    virtual void MakeTypicalNoise() {
        cout << "Woo, woo, woow!";
        cout << " ... ";
        Dog::MakeTypicalNoise();
    }
};

int main() {

    Animal* apObject[] = { new Cat(), new Dog(), new Doberman() };

    const   int cnAnimals = sizeof(apObject)/sizeof(Animal*);
    for ( int i = 0; i < cnAnimals; i++ ) {
        apObject[i]->MakeTypicalNoise();
    }
    for ( int i = 0; i < cnAnimals; i++ ) {
        delete apObject[i];
    }
    return 0;
}

उदाहरण कोड का आउटपुट

Meow!
Woof!
Woo, woo, woow! ... Woof!

कोड उदाहरण का यूएमएल वर्ग आरेख

कोड उदाहरण का यूएमएल वर्ग आरेख


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

2

यहां पूरा उदाहरण दिया गया है कि यह दिखाता है कि आभासी पद्धति का उपयोग क्यों किया जाता है।

#include <iostream>

using namespace std;

class Basic
{
    public:
    virtual void Test1()
    {
        cout << "Test1 from Basic." << endl;
    }
    virtual ~Basic(){};
};
class VariantA : public Basic
{
    public:
    void Test1()
    {
        cout << "Test1 from VariantA." << endl;
    }
};
class VariantB : public Basic
{
    public:
    void Test1()
    {
        cout << "Test1 from VariantB." << endl;
    }
};

int main()
{
    Basic *object;
    VariantA *vobjectA = new VariantA();
    VariantB *vobjectB = new VariantB();

    object=(Basic *) vobjectA;
    object->Test1();

    object=(Basic *) vobjectB;
    object->Test1();

    delete vobjectA;
    delete vobjectB;
    return 0;
}

1

दक्षता के बारे में, शुरुआती-बाध्यकारी कार्यों के रूप में आभासी फ़ंक्शन थोड़ा कम कुशल हैं।

"इस आभासी कॉल तंत्र को" सामान्य फ़ंक्शन कॉल "तंत्र के रूप में लगभग कुशल बनाया जा सकता है (25% के भीतर)। इसका स्थान ओवरहेड आभासी कार्यों के साथ कक्षा के प्रत्येक ऑब्जेक्ट में एक पॉइंटर है और प्रत्येक ऐसे वर्ग के लिए एक vtbl है" [ A बजरने स्ट्रॉस्ट्रुप द्वारा सी ++ का दौरा ]


2
लेट बाइंडिंग सिर्फ फंक्शन कॉल को धीमा नहीं करता है, यह रन फंक्शन को रन टाइम तक अज्ञात बना देता है, इसलिए फंक्शन कॉल के पार अनुकूलन लागू नहीं किया जा सकता है। यह सब कुछ बदल सकता है। f.ex. ऐसे मामलों में जहां मूल्य प्रसार बहुत सारे कोड को हटा देता है (यह सोचें if(param1>param2) return cst;कि कंपाइलर कुछ मामलों में पूरे फंक्शन कॉल को कम कर सकता है)।
जिज्ञासु

1

इंटरफेस डिजाइन में आभासी तरीकों का उपयोग किया जाता है। उदाहरण के लिए विंडोज में नीचे की तरह IUnaware नामक एक इंटरफ़ेस है:

interface IUnknown {
  virtual HRESULT QueryInterface (REFIID riid, void **ppvObject) = 0;
  virtual ULONG   AddRef () = 0;
  virtual ULONG   Release () = 0;
};

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


the run-time is aware of the three methods and expects them to be implementedजैसा कि वे शुद्ध आभासी हैं, कोई उदाहरण बनाने का कोई तरीका नहीं है IUnknown, और इसलिए सभी उपवर्गों को केवल संकलन करने के लिए ऐसे सभी तरीकों को लागू करना होगा । उन्हें लागू न करने का कोई खतरा नहीं है और केवल रनटाइम के दौरान ही पता चल जाएगा (लेकिन जाहिर है कि कोई उन्हें गलत तरीके से लागू कर सकता है )। और वाह, आज मैंने #defineशब्द के साथ विंडोज सा मैक्रो सीखा interface, शायद इसलिए क्योंकि उनके उपयोगकर्ता Iनाम में उपसर्ग नहीं देख सकते हैं या (बी) वर्ग को देखने के लिए इसे एक इंटरफ़ेस देख सकते हैं। ऊग
अंडरस्कोर_ड

1

मुझे लगता है कि आप इस तथ्य का उल्लेख कर रहे हैं कि एक बार एक विधि को आभासी घोषित करने के बाद आपको ओवरराइड में 'वर्चुअल' कीवर्ड का उपयोग करने की आवश्यकता नहीं है।

class Base { virtual void foo(); };

class Derived : Base 
{ 
  void foo(); // this is overriding Base::foo
};

यदि आप बेस की फू घोषणा में 'वर्चुअल' का उपयोग नहीं करते हैं तो डेरेवड का फयू सिर्फ इसे छायांकित करेगा।


1

यहां पहले दो उत्तरों के लिए C ++ कोड का मर्ज किया गया संस्करण है।

#include        <iostream>
#include        <string>

using   namespace       std;

class   Animal
{
        public:
#ifdef  VIRTUAL
                virtual string  says()  {       return  "??";   }
#else
                string  says()  {       return  "??";   }
#endif
};

class   Dog:    public Animal
{
        public:
                string  says()  {       return  "woof"; }
};

string  func(Animal *a)
{
        return  a->says();
}

int     main()
{
        Animal  *a = new Animal();
        Dog     *d = new Dog();
        Animal  *ad = d;

        cout << "Animal a says\t\t" << a->says() << endl;
        cout << "Dog d says\t\t" << d->says() << endl;
        cout << "Animal dog ad says\t" << ad->says() << endl;

        cout << "func(a) :\t\t" <<      func(a) <<      endl;
        cout << "func(d) :\t\t" <<      func(d) <<      endl;
        cout << "func(ad):\t\t" <<      func(ad)<<      endl;
}

दो अलग-अलग परिणाम हैं:

#Define वर्चुअल के बिना , यह संकलन समय पर बांधता है। पशु * विज्ञापन और कवक (पशु *) सभी पशु के कहना () विधि को इंगित करते हैं।

$ g++ virtual.cpp -o virtual
$ ./virtual 
Animal a says       ??
Dog d says      woof
Animal dog ad says  ??
func(a) :       ??
func(d) :       ??
func(ad):       ??

#Define वर्चुअल के साथ , यह रन टाइम पर बांधता है। डॉग * d, एनीमल * ad और func (एनिमल *) पॉइंट / रेफर टू डॉग्स कहता है () विधि क्योंकि डॉग उनका ऑब्जेक्ट टाइप है। जब तक [डॉग का कहना है () "वूफ़"] विधि को परिभाषित नहीं किया जाता है, तब तक यह पहले वर्ग के पेड़ में खोजा जाएगा, अर्थात व्युत्पन्न वर्ग अपने आधार वर्गों के तरीकों को ओवरराइड कर सकते हैं [पशु का कहना है ()]।

$ g++ virtual.cpp -D VIRTUAL -o virtual
$ ./virtual 
Animal a says       ??
Dog d says      woof
Animal dog ad says  woof
func(a) :       ??
func(d) :       woof
func(ad):       woof

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

class   Animal:
        def     says(self):
                return  "??"

class   Dog(Animal):
        def     says(self):
                return  "woof"

def     func(a):
        return  a.says()

if      __name__ == "__main__":

        a = Animal()
        d = Dog()
        ad = d  #       dynamic typing by assignment

        print("Animal a says\t\t{}".format(a.says()))
        print("Dog d says\t\t{}".format(d.says()))
        print("Animal dog ad says\t{}".format(ad.says()))

        print("func(a) :\t\t{}".format(func(a)))
        print("func(d) :\t\t{}".format(func(d)))
        print("func(ad):\t\t{}".format(func(ad)))

आउटपुट है:

Animal a says       ??
Dog d says      woof
Animal dog ad says  woof
func(a) :       ??
func(d) :       woof
func(ad):       woof

जो C ++ के वर्चुअल डिफाइन के समान है। ध्यान दें कि और विज्ञापन दो अलग-अलग पॉइंटर वैरिएबल हैं जो एक ही डॉग उदाहरण की ओर इशारा करते हैं। एक्सप्रेशन (विज्ञापन d) सही है और उनके मान समान हैं। मुख्य .Dog ऑब्जेक्ट 0xb79f72cc> पर है।


0

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


0

क्या आप फंक्शन पॉइंटर्स से परिचित हैं? वर्चुअल फ़ंक्शंस एक समान विचार है, इसके अलावा आप आसानी से वर्चुअल फ़ंक्शंस (क्लास के सदस्यों के रूप में) डेटा को बाँध सकते हैं। फ़ंक्शन को इंगित करने के लिए डेटा को बाँधना उतना आसान नहीं है। मेरे लिए, यह मुख्य वैचारिक भेद है। यहाँ बहुत सारे अन्य उत्तर सिर्फ इसलिए कह रहे हैं "क्योंकि ... बहुरूपता!"


-1

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

 class base {
 public:
 void helloWorld() { std::cout << "Hello World!"; }
  };

 class derived: public base {
 public:
 void helloWorld() { std::cout << "Greetings World!"; }
 };

 int main () {
      base hwOne;
      derived hwTwo = new derived();
      base->helloWorld(); //prints "Hello World!"
      derived->helloWorld(); //prints "Hello World!"

ठीक है, इसलिए कि हम जानते हैं। अब इसे सदस्य-फ़ंक्शन पॉइंटर्स के साथ करने का प्रयास करते हैं:

 #include <iostream>
 using namespace std;

 class base {
 public:
 void helloWorld() { std::cout << "Hello World!"; }
 };

 class derived : public base {
 public:
 void displayHWDerived(void(derived::*hwbase)()) { (this->*hwbase)(); }
 void(derived::*hwBase)();
 void helloWorld() { std::cout << "Greetings World!"; }
 };

 int main()
 {
 base* b = new base(); //Create base object
 b->helloWorld(); // Hello World!
 void(derived::*hwBase)() = &derived::helloWorld; //create derived member 
 function pointer to base function
 derived* d = new derived(); //Create derived object. 
 d->displayHWDerived(hwBase); //Greetings World!

 char ch;
 cin >> ch;
 }

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

दूसरी ओर, वर्चुअल फ़ंक्शन, जबकि उनके पास कुछ फ़ंक्शन-पॉइंटर ओवरहेड हो सकते हैं, नाटकीय रूप से चीजों को सरल करते हैं।

EDIT: एक अन्य विधि है जो eddietree द्वारा समान है: c ++ वर्चुअल फ़ंक्शन बनाम सदस्य फ़ंक्शन पॉइंटर (प्रदर्शन तुलना)

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