چندریختی

چندریختی در C++ توسط توابع مجازی پیاده سازی می شود. تابع مجازی تابع عضوی است که انتظار می رود در کلاس های مشتق شده دوباره تعریف شود. درک چندریختی بدون استفاده از توارث و انتزاع غیرممکن است. چندریختی (polymorphism) یکی از ویژگی های زبان های شیءگراست. به واسطه چندریختی توابع می توانند به شیوه های مختلف پیاده سازی شوند ولی از طریق یک اسم یکسان در دسترس قرار بگیرند. چندریختی در ++C به دو شکل پشتیبانی می شود؛ در زمان کامپایل و در زمان اجرا. سربارگذاری توابع و عملگرها نمونه هائی از چندریختی در زمان کامپایل هستند. چندریختی در زمان اجرا با تلفیق وراثت و توابع مجازی حاصل می شود.

 

تابع مجازی

یک تابع مجازی (virtual function) به تابعی گفته می شود که در کلاس پایه اعلان شده است و مجددا توسط کلاس مشتق شده تعریف می شود. اگر کلاس پایه شامل تابع مجازی باشد کلاس مشتق شده می تواند این تابع مجازی را با توجه به نیازهای خود مجدد تعریف می کند.

برای ایجاد یک تابع مجازی کلمه کلیدی virtual در ابتدای اعلان تابع اضافه می شود.


مثال. تابع مجازی show در کلاس پایه دوباره در کلاس مشتق شده اعلان شده است.

#include <iostream.h>
class Base {
public:
virtual void Show() {
cout << “Base::Show” << endl;
}
};

class Derived : public Base {
public:
void Show() {
cout << “Derived::Show” << endl;
}
void Value(int i) {
cout << i << endl;
}
};

int main() {
Derived d;
d.Show();
}


نکته. به کلاسی که دارای یک تابع مجازی باشد کلاس پلی مورفیک (polymorphic) می گویند.
نکته. وقتی یک تابع مجازی در کلاس مشتق شده دوباره تعریف می شود نوشتن عبارت virtual در کلاس مجازی الزامی نیست.
نکته. اگر کلاس مشتق شده تابع مجازی کلاس پایه را مجددا تعریف نکند نسخه پیش فرض کلاس پایه استفاده می شود.
نکته. نمی توانید مقدار برگشتی یک تابع مجازی را در طی ابطال تغییر دهید. اگر نوع برگشتی تابع override با تابع مجازی متفاوت باشد نوع پارامترها هم باید تفاوت داشته باشند.


اشاره گر به نوع پایه و مشتق شده

در مثال بالا تابع show کلاس مشتق شده تابع show کلاس پایه را باطل (override) می کند. بنابراین وقتی شیئی از کلاس مشتق شده تعریف می شود کامپایلر تابع show کلاس مشتق شده را فراخوانی می کند. برای دسترسی به تابع کلاس پایه از اشاره گرها می توان استفاده کرد. با اشاره گری به کلاس مشتق شده یا به کلاس پایه می توان تابع مجازی و نسخه مشتق شده آن را اجرا کرد.

اشاره گر به کلاس پایه و کلاس های مشتق شده با هم در ارتباطند. اشاره گر به کلاس پایه می تواند برای اشاره به شیئی از هر کلاسی که از آن مشتق شده است بکار گرفته شود.


مثال. فرض کنید کلاس پایه Base و کلاس مشتق شده از آن Derived نامگذاری شده اند. هر اشاره گری به کلاس Base می تواند به Derived هم اشاره کند.

Base *p;
Base b;
Derived d;
p= &b;   //Points to object of Base
p= &d;   //Points to object of Derived

اشاره گر پایه p می تواند به کلیه اعضای Derived که از Base به ارث گرفته است دسترسی پیدا کند اما نمی تواند به اعضای مشخص شده کلاس مشتق شده دسترسی داشته باشد.


گرفتن آدرس یک شیء و کار با آن به صورت آدرس نوع پایه را upcasting می نامند.


مثال. در برنامه زیر اشاره گر پایه p به اعضای ارث گرفته شده کلاس مشتق شده دسترسی دارد اما نمی تواند به توابع get و showDerived کلاس مشتق شده دسترسی پیدا کند.

#include <iostream.h>
class Base {
int i;
public:
void set(int ii) { i = ii; }
void showBase() { cout << “Base ” << i << endl; }
};

class Derived : public Base {
int j;
public:
void get() { cin >> j; }
void showDerived() { cout << “Derived ” << j << endl; }
};

int main() {
Base b, *p;
Derived d, *q;
p= &b;          //address of Base Class
p->set(5);    //access b via pointer
p= &d;         //address of Base Class
p->set(6);   //access d via base pointer
b.showBase();    //acess directly
d.showBase();
q=&d;         //address od Derived Class
q->get();    //access d via pointer
p->showBase();       //either p or q can be used here
q->showDerived();    //only p can not be used here
}


وقتی فراخوانی تابعی توسط اشاره گر صورت می گیرد قوانین زیر رعایت می شود:

• یک فراخوانی به تابع مجازی طبق نوع شیء تفکیک می شود.
• یک فراخوانی به تابع غیرمجازی طبق نوع اشاره گر تبدیل می شود.


نکته. هرچند اشاره گر کلاس پایه می تواند برای اشاره به نوع مشتق شده استفاده شود ولی عکس این موضوع مصداق ندارد.
نکته. در صورتی که بخواهیم توسط اشاره گر پایه به مولفه های یک کلاس مشتق شده دسترسی پیدا کنیم باید نوع آنرا به نوع کلاس مشتق شده تغییر دهید.


مثال.

((Derived *)p) ->showDerived();

مثال.مکانیسم فراخوانی تابع مجازی می تواند با بیان صریح نام تابع و عملگر :: ملغی شود.

Derived *p, d;
p=&d;
p->Base::Show();    //explicit qualification.


کلاس های مجرد و توابع مجازی محض

اغلب در طراحی نیاز است کلاس پایه تنها واسطی برای کلاس های مشتق شده خودش باشد و نمی خواهید کسی شیئی از نوع پایه ایجاد کند. این عمل با مجرد (abstract) کردن کلاس صورت می گیرد. برای مجرد کردن کافیست کلاس پایه حداقل یک تابع مجازی محض را داشته باشد.

تابع مجازی محض (pure virtual) تابعی است که در کلاس پایه اعلان شده است و تعریفی ندارد، هر کلاس مشتق شده باید نسخه مربوط به خود را تعریف کند. اگر کسی سعی کند شیئی از نوع کلاس مجرد ایجاد کند کامپایلر جلوی آنرا می گیرد.

بدنبال اعلان یک تابع مجازی محض =۰ قرار می گیرد.

وقتی یک کلاس مجرد به ارث برده می شود کلیه توابع مجازی محض باید پیاده سازی شوند یا کلاس مشتق شده هم باید مجرد باشد. ایجاد یک تابع مجازی محض اجازه می دهد تابعی را بدون نیاز به داشتن بدنه در کلاس پایه اضافه کنید. درضمنی که کلاس های مشتق شده را الزام به تعریف آن میکند.


مثال.

#include <iostream.h>
enum note { middleC, Csharp, Cflat };

class Instrument {
public:
virtual void play(note) = 0;
virtual char* what() = 0;
};
class Wind : public Instrument {
public:
void play(note){
cout << “Wind::play” << endl;
}
char* what() { return “Wind”; }
void adjust(int) {cout << “adjusted” << endl;}
};

int main() {
Wind flute;
cout << flute.what();
flute.play(Cflat);
}


سازنده ها و مخرب ها درچندریختی

سازنده ها وظیفه خاص تکه تکه قرار دادن یک شیء در کنار هم را دارند. ابتدا سازنده پایه سپس سازنده های مشتق شده به ترتیب وراثت فراخوانی می شوند. مخرب ها هم باید یک شیء را که به سلسله مراتبی از کلاس ها تعلق دارد خراب کنند. برای اینکار کامپایلر کلیه مخرب ها را به ترتیب عکس فراخوانی سازنده ها اجرا می کند. به این ترتیب می توانید در مخرب تابع عضو کلاس پایه را درصورت نیاز فراخوانی کنید.

بخاطر داشته باشید این سلسله مراتب تنها در فراخوانی سازنده ها و مخرب ها اتفاق می افتد. در کلیه توابع دیگر تنها همان تابع فراخوانی می شود چه مجازی باشد چه نباشد.

نمی توانید کلمه virtual را قبل از تابع سازنده قرار دهید اما توابع مخرب اغلب مجازی هستند.


مثال. برنامه زیر تفاوت مخرب مجازی و غیر مجازی را نشان می دهد.

#include <iostream.h>
class Base1 {
public:
~Base1() { cout << “~Base1()\n”; }
};

class Derived1 : public Base1 {
public:
~Derived1() { cout << “~Derived1()\n”; }
};

class Base2 {
public:
virtual ~Base2() { cout << “~Base2()\n”; }
};

class Derived2 : public Base2 {
public:
~Derived2() { cout << “~Derived2()\n”; }
};

int main() {
Base1* bp = new Derived1; // Upcast
delete bp;
Base2* b2p = new Derived2; // Upcast
delete b2p;
}

مثال. تابع مخرب مجازی محض مجاز است به شرطی که بدنه ای برای آن فراهم شده باشد.

#include <iostream.h>
class AbstractBase {
public:
virtual ~AbstractBase() = 0;
};
AbstractBase::~AbstractBase() {}
class Derived : public AbstractBase {};
// No overriding of destructor necessary?
int main()
{
Derived d;
}

 

 

 

 

منبع : پی سی کد