شرح - visual c++




لماذا نحتاج إلى وظائف ظاهرية في C++؟ (14)

أنا أتعلم لغة C ++ وأبدأ للتو في وظائف افتراضية.

من ما قرأته (في الكتاب وعبر الإنترنت) ، فإن الوظائف الافتراضية هي وظائف في الطبقة الأساسية التي يمكنك تجاوزها في الفئات المشتقة.

ولكن في وقت سابق من هذا الكتاب ، عندما كنت أتعلم عن الميراث الأساسي ، تمكنت من تجاوز الوظائف الأساسية في الطبقات المشتقة دون استخدام virtual .

ما الذي أفتقده هنا؟ أعلم أن هناك المزيد للوظائف الافتراضية ، ويبدو أنها مهمة لذا أريد أن أكون واضحًا بشأن ما هو بالضبط. لا يمكنني العثور على إجابة مباشرة عبر الإنترنت.


لماذا نحتاج إلى أساليب افتراضية في C ++؟

جواب سريع:

  1. فإنه يوفر لنا واحدة من "المكونات" اللازمة 1 للبرمجة وجوه المنحى .

في Bjarne Stroustrup C ++ Programming: Principle and Practice، (14.3):

توفر الدالة الظاهرية إمكانية تحديد دالة في فئة أساسية ولها وظيفة بنفس الاسم والنوع في فئة مشتقة تسمى عندما يقوم المستخدم باستدعاء وظيفة الفئة الأساسية. غالباً ما يطلق عليه تعدد الأشكال وقت التشغيل أو إرسال حيوي أو إرسال وقت التشغيل لأنه يتم تحديد الدالة تسمى في وقت التشغيل استناداً إلى نوع الكائن المستخدم.

  1. وهو أسرع تنفيذ أكثر فعالية إذا كنت تحتاج إلى استدعاء وظيفة ظاهرية 2 .

للتعامل مع مكالمة افتراضية ، يحتاج المرء إلى واحد أو أكثر من أجزاء البيانات المتعلقة بالكائن المشتق 3 . الطريقة التي تتم عادة هي إضافة عنوان جدول الوظائف. يشار إلى هذا الجدول عادةً بجدول ظاهري أو جدول ظاهري ظاهريًا وغالباً ما يطلق على عنوانه اسم المؤشر الظاهري . تحصل كل وظيفة ظاهرية على فتحة في الجدول الظاهري. بالاعتماد على نوع كائن المستدعي (مشتق) ، تستدعي الدالة الظاهرية بدورها التجاوز المعني.

1. استخدام التوريث ، وتعدد الأشكال وقت التشغيل ، والتغليف هو التعريف الأكثر شيوعا للبرمجة وجوه المنحى .

2. لا يمكنك برمجة الوظائف لتكون أسرع أو لاستخدام ذاكرة أقل باستخدام ميزات اللغة الأخرى للاختيار من بين البدائل في وقت التشغيل. Bjarne Stroustrup C ++ Programming: Principles and Practice. (14.3.1) .

.3 ﺷﻲء ﻳﻘﻮل ﻣﺎ اﻟﺪاﻟﺔ اﻟﺘﻲ ﻳﺘﻢ اﺳﺘﺪﻋﺎﺋﻬﺎ ﻓﻌﻼً ﻋﻨﺪﻣﺎ ﻧﺘﺼﻞ ﺑﺎﻟﻔﺌﺔ اﻷﺳﺎﺳﻴﺔ اﻟﺘﻲ ﺗﺤﺘﻮي ﻋﻠﻰ اﻟﻮﻇﻴﻔﺔ اﻟﻈﺎهﺮﻳﺔ


أنت بحاجة إلى طرق افتراضية للابلاغ الآمن والبساطة والإيجاز .

هذا ما تفعله الأساليب الافتراضية: فهي تهبط بأمان ، مع رمز بسيط وموجز على ما يبدو ، وتجنب القوالب اليدوية غير الآمنة في الكود الأكثر تعقيدًا والمطوَّر الذي قد يكون لديك.

طريقة غير افتراضية ⇒ ربط ثابت

التعليمة البرمجية التالية عمداً "غير صحيحة". لا تعلن عن طريقة 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 ليس ظاهريًا.

الطريقة الافتراضية binding الديناميكية.

يضمن إعلان 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 ، ولكن يتم بدلاً من ذلك وضع علامة عليها. يمكن وضع علامة عليها بشكل 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;
}

إحدى الطرق الإيجابية للنظر إلى هذا هو ، إذا واجهت هبوطا غير آمن وتعقيدات ووسامة كما هو مذكور أعلاه ، فغالباً ما يمكن أن تساعد طريقة أو طرق افتراضية فعلاً.


أود إضافة استخدام آخر للوظيفة الظاهرية على الرغم من أنه يستخدم نفس المفهوم للإجابات المذكورة أعلاه ، ولكني أعتقد أنه يستحق الذكر.

الظاهري الدكان

خذ بعين الاعتبار هذا البرنامج أدناه ، دون تعريف destructor فئة أساسية كـ الظاهري؛ ذاكرة Cat قد لا يتم تنظيفها.

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

إذا كانت الفئة الأساسية هي Base ، والفئة المشتقة هي Der ، يمكن أن يكون لديك مؤشر Base *p والذي يشير بالفعل إلى مثيل Der . عند الاتصال بـ p->foo(); إذا لم يكن foo افتراضيًا ، فإن إصدار Base الخاص به ينفذ ، متجاهلاً حقيقة أن p يشير بالفعل إلى Der . إذا كان foo ظاهريًا ، فإن p->foo() يُنفذ تجاوز foo لأقصى درجة ، مع الأخذ في الاعتبار تمامًا الطبقة الفعلية للعنصر المُشار إليه. لذا فإن الفرق بين الظاهرية والغير افتراضية هو في الواقع أمر حاسم: الأول يسمح بتعدد polymorphism وقت التشغيل ، وهو المفهوم الأساسي للبرمجة OO ، في حين أن الأخير لا يفعل ذلك.


الحاجة للوظيفة الافتراضية الموضحة [سهلة الفهم]

#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.

وبالتالي مع وظيفة افتراضية يمكنك تحقيق تعدد الأشكال وقت التشغيل.


بدون "افتراضية" تحصل على "الربط المبكر". يتم تحديد تطبيق الطريقة التي يتم استخدامها في وقت التحويل البرمجي استنادًا إلى نوع المؤشر الذي تتصل به.

مع "افتراضية" تحصل على "الربط المتأخر". يتم تحديد تنفيذ الطريقة التي يتم استخدامها في وقت التشغيل استنادًا إلى نوع الكائن المدبَّب - ما تم إنشاؤه أصلاً. هذا ليس بالضرورة ما كنت تعتقد استناداً إلى نوع المؤشر الذي يشير إلى هذا الكائن.

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"

تحرير - انظر هذا السؤال .

أيضا - يغطي هذا البرنامج التعليمي الربط المبكر والمتأخر في C ++.


تُستخدم الطرق الافتراضية في تصميم الواجهة. على سبيل المثال في ويندوز هناك واجهة تسمى IUnknown مثل أدناه:

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

يتم ترك هذه الأساليب إلى واجهة المستخدم للتنفيذ. وهي ضرورية لإنشاء وتدمير بعض الكائنات التي يجب أن ترث IUnknown. في هذه الحالة ، تكون وقت التشغيل على دراية بالطرق الثلاثة وتتوقع تنفيذها عند استدعائها. بمعنى أنها تعمل بمثابة عقد بين الكائن نفسه وأي استخدامات هذا الكائن.


حول الكفاءة ، تكون الوظائف الافتراضية أقل كفاءة بشكل طفيف مثل وظائف الربط المبكر.

"يمكن جعل آلية الاتصال الافتراضية هذه بنفس كفاءة آلية" استدعاء الوظيفة العادية "(في حدود 25٪). فضاءه الفضائي هو مؤشر واحد في كل كائن من الصف مع وظائف ظاهرية بالإضافة إلى vtbl واحد لكل فئة من هذا القبيل" [ A جولة في C ++ من Bjarne Stroustrup]


عندما يكون لديك دالة في الفئة الأساسية ، يمكنك Redefine أو Override في الفئة المشتقة.

إعادة تعريف طريقة : يتم توفير تطبيق جديد لأسلوب الفئة الأساسية في الفئة المشتقة. لا يسهل Dynamic binding .

تجاوز أسلوب : Redefining virtual method للفئة الأساسية في الفئة المشتقة. الطريقة الافتراضية تسهل الربط الديناميكي .

لذلك عندما قلت:

لكن في وقت سابق من الكتاب ، عندما كنت أتعلم عن الميراث الأساسي ، تمكنت من تجاوز الأساليب الأساسية في الطبقات المشتقة دون استخدام "ظاهري".

لم تكن تتجاهلها لأن الطريقة في الفئة الأساسية لم تكن افتراضية ، بل كنت تعيد تعريفها


لدي إجابة في شكل محادثة لتكون قراءة أفضل:

لماذا نحتاج إلى وظائف افتراضية؟

بسبب تعدد الأشكال.

ما هو تعدد الأشكال؟

حقيقة أن مؤشرًا أساسيًا يمكن أن يشير أيضًا إلى كائنات الكتابة المشتقة.

كيف يؤدي هذا التعريف لتعدد الأشكال إلى الحاجة إلى وظائف افتراضية؟

حسنا ، من خلال الربط المبكر .

ما هو الربط المبكر؟

الربط المبكر (الربط وقت التجميع) في C ++ يعني أنه تم إصلاح استدعاء دالة قبل تنفيذ البرنامج.

وبالتالي...؟

لذا إذا كنت تستخدم نوعًا أساسيًا كمعلمة لدالة ما ، فسوف يتعرف المحول البرمجي على واجهة القاعدة فقط ، وإذا قمت باستدعاء هذه الوظيفة بأية وسيطات من الطبقات المشتقة ، فسيتم تقطيعها ، وهو ما لا يحدث.

إذا لم يكن ما نريد أن يحدث ، فلماذا هذا مسموح؟

لأننا نحتاج إلى تعدد الأشكال!

ما هي فائدة تعدد الأشكال بعد ذلك؟

يمكنك استخدام مؤشر نوع أساسي كمعلمة لدالة واحدة ، وبعد ذلك في وقت تشغيل البرنامج ، يمكنك الوصول إلى كل من واجهات الكتابة المشتقة (على سبيل المثال وظائف الأعضاء الخاصة بها) دون أي مشاكل ، وذلك باستخدام إلغاء الإشارة إلى ذلك المفرد. مؤشر القاعدة.

ما زلت لا أعرف ما هي الوظائف الافتراضية جيدة ل ...! وكان هذا سؤالي الأول!

حسنًا ، هذا لأنك طرحت سؤالك سريعًا جدًا!

لماذا نحتاج إلى وظائف افتراضية؟

افترض أنك قمت باستدعاء دالة بمؤشر أساس ، والتي تحتوي على عنوان كائن من إحدى الفئات المشتقة الخاصة به. كما تحدثنا عنها أعلاه ، في وقت التشغيل ، يحصل هذا المؤشر على قيمة ، حتى الآن جيد جدا ، ومع ذلك ، فإننا نتوقع طريقة (= = وظيفة عضو) "من الطبقة المستمدة لدينا" ليتم تنفيذها! ومع ذلك ، يتم تعريف نفس الأسلوب (واحد يحتوي على نفس الرأس) بالفعل في الفئة الأساسية ، فلماذا يجب أن يكلف برنامجك عناء اختيار الطريقة الأخرى؟ وبعبارة أخرى أعني ، كيف يمكنك أن تخبر هذا السيناريو عن ما كنا نراه عادة يحدث من قبل؟

الجواب المختصر هو "وظيفة عضو افتراضي في الأساس" ، والإجابة لفترة أطول قليلاً هي أنه "في هذه الخطوة ، إذا كان البرنامج يرى وظيفة ظاهرية في الفئة الأساسية ، فإنه يعلم (يدرك) أنك تحاول استخدام تعدد الأشكال "وهكذا يذهب إلى الطبقات المشتقة (باستخدام v-table ، وهو شكل من أشكال الربط المتأخر) للعثور على طريقة أخرى مع نفس الرأس ، ولكن مع-بشكل غير متوقع- تنفيذ مختلف.

لماذا تنفيذ مختلف؟

أنت مفصل الرأس! اذهب الى قراءة كتاب جيد !

موافق ، الانتظار الانتظار الانتظار ، لماذا يزعج الشخص استخدام المؤشرات الأساسية ، عندما يمكنه ببساطة استخدام مؤشرات نوع المشتقة؟ أنت القاضي ، هل كل هذا الصداع يستحق كل هذا العناء؟ انظر إلى هذين القصاصين:

// 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();

وفي الواقع ، قد يكون هذا مثالًا مفتعلًا أيضًا!


وتستخدم وظائف الظاهري لدعم تشغيل تعدد الأشكال مرة .

بمعنى ، الكلمة الأساسية الظاهرية تخبر المترجم بعدم اتخاذ القرار (لربط الدالة) في وقت التحويل البرمجي ، بدلاً من تأجيله لوقت التشغيل " .

  • يمكنك عمل وظيفة ظاهرية بسبق الكلمة الرئيسية virtual في تعريف الفئة الأساسي الخاص بها. فمثلا،

     class Base
     {
        virtual void func();
     }
    
  • عندما يكون لـ Base Class وظيفة عضو ظاهري ، يمكن لأي فئة ترث من Class Base أن تعيد تعريف الوظيفة بنفس النموذج الأصلي فقط ، أي أنه يمكن إعادة تعريف الوظيفة فقط ، وليس واجهة الدالة.

     class Derive : public Base
     {
        void func();
     }
    
  • يمكن استخدام مؤشر فئة أساسية للإشارة إلى كائن فئة الأساسي بالإضافة إلى كائن فئة مشتقة.

  • عندما يتم استدعاء الدالة الظاهرية باستخدام مؤشر فئة أساسية ، يقرر المحول البرمجي في وقت التشغيل أي إصدار من الدالة - أي إصدار فئة الأساس أو إصدار فئة مشتقة متصالبة - يتم استدعاؤه. وهذا ما يسمى Run Time Polymorphism .

يخبر الكلمة الافتراضية المحول البرمجي أنه لا يجب إجراء الربط المبكر. بدلاً من ذلك ، يجب عليه تثبيت كافة الآليات اللازمة لتنفيذ الربط المتأخر تلقائيًا. لإنجاز هذا ، ينشئ compiler1 النموذجي جدول مفرد (يسمى VTABLE) لكل فئة يحتوي على دالات ظاهرية. يضع المحول البرمجي عناوين الوظائف الظاهرية لتلك الفئة المعينة في VTABLE. في كل صف مع وظائف ظاهرية ، فإنه يضع سرا مؤشر ، يسمى vpointer (يختصر كـ VPTR) ، والذي يشير إلى VTABLE لهذا الكائن. عندما تقوم بإجراء استدعاء دالة ظاهرية من خلال مؤشر من الفئة الأساسية ، يقوم المحول البرمجي بإدراج التعليمة برمجيًا بهدوء لجلب VPTR والبحث عن عنوان الدالة في VTABLE ، وبالتالي استدعاء الوظيفة الصحيحة والتسبب في الربط المتأخر.

مزيد من التفاصيل في هذا الرابط http://cplusplusinterviews.blogspot.sg/2015/04/virtual-mechanism.html


Here is complete example that illustrates why virtual method is used.

#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;
}

We need virtual methods for supporting "Run time Polymorphism". When you refer to a derived class object using a pointer or a reference to the base class, you can call a virtual function for that object and execute the derived class's version of the function.





virtual-functions