runtime polymorphism c
Подробно изследване на полиморфизма по време на работа в C ++.
Полиморфизмът по време на работа е известен също като динамичен полиморфизъм или късно свързване. В полиморфизма по време на изпълнение, извикването на функция се разрешава по време на изпълнение.
За разлика от това, за да компилира време или статичен полиморфизъм, компилаторът извежда обекта по време на изпълнение и след това решава кое извикване на функцията да се свърже с обекта. В C ++, полиморфизмът по време на изпълнение се реализира, като се замени методът.
В този урок ще разгледаме подробно всичко за полиморфизма по време на изпълнение.
=> Проверете ВСИЧКИ уроци за C ++ тук.
Какво ще научите:
- Замяна на функцията
- Виртуална функция
- Работа на виртуална маса и _vptr
- Чисти виртуални функции и абстрактен клас
- Виртуални деструктори
- Заключение
- Препоръчително четене
Замяна на функцията
Функцията заменяне е механизмът, чрез който функция, дефинирана в базовия клас, отново се дефинира в производния клас. В този случай казваме, че функцията е заменена в производния клас.
Трябва да помним, че заменянето на функцията не може да се направи в рамките на клас. Функцията е заменена само в производния клас. Следователно наследяването трябва да присъства за заместване на функцията.
Второто нещо е, че функцията от базовия клас, който заместваме, трябва да има същия подпис или прототип, т.е. трябва да има същото име, същия тип връщане и същия списък с аргументи.
Нека видим пример, който демонстрира замяна на метода.
#include using namespace std; class Base { public: void show_val() { cout << 'Class::Base'< Изход:
Клас :: База
Клас :: Производно
В горната програма имаме основен клас и производен клас. В базовия клас имаме функция show_val, която е заменена в производния клас. В основната функция ние създаваме обект всеки от Base и Derived клас и извикваме функцията show_val с всеки обект. Той произвежда желаната продукция.
Горното свързване на функции, използващи обекти от всеки клас, е пример за статично обвързване.
Сега нека видим какво се случва, когато използваме указателя на базовия клас и присвоим обекти на производен клас като негово съдържание.
Примерната програма е показана по-долу:
#include using namespace std; class Base { public: void show_val() { cout << 'Class::Base'; } }; class Derived:public Base { public: void show_val() //overridden function { cout <<'Class::Derived'; } }; int main() { Base* b; //Base class pointer Derived d; //Derived class object b = &d; b->show_val(); //Early Binding }
Изход:
Клас :: База
Сега виждаме, че изходът е “Class :: Base”. Така че независимо от какъв тип обект се съхранява основния указател, програмата извежда съдържанието на функцията на класа, чийто основен указател е типът. В този случай се извършва и статично свързване.
За да направим изхода на основния указател, правилно съдържание и правилно свързване, ние се насочваме към динамично свързване на функции. Това се постига с помощта на механизма за виртуални функции, който е обяснен в следващия раздел.
Виртуална функция
Тъй като заменената функция трябва да бъде динамично свързана към тялото на функцията, ние правим функцията на основния клас виртуална, използвайки ключовата дума „virtual“. Тази виртуална функция е функция, която е заменена в производния клас и компилаторът извършва късно или динамично свързване за тази функция.
Сега нека модифицираме горната програма, за да включим виртуалната ключова дума, както следва:
#include using namespace std;. class Base { public: virtual void show_val() { cout << 'Class::Base'; } }; class Derived:public Base { public: void show_val() { cout <<'Class::Derived'; } }; int main() { Base* b; //Base class pointer Derived d; //Derived class object b = &d; b->show_val(); //late Binding }
Изход:
Клас :: Производно
Така че в горната дефиниция на клас на Base направихме функцията show_val като „виртуална“. Тъй като функцията на базовия клас се прави виртуална, когато присвояваме обект на производен клас на показалеца на базовия клас и извикваме функцията show_val, свързването се случва по време на изпълнение.
По този начин, тъй като указателят на базовия клас съдържа обект на производен клас, тялото на функцията show_val в извлечения клас е обвързано с функцията show_val и следователно изхода.
В C ++ заменената функция в производен клас също може да бъде частна. Компилаторът проверява само типа на обекта по време на компилиране и обвързва функцията по време на изпълнение, следователно не прави никаква разлика, дори ако функцията е публична или частна.
Имайте предвид, че ако дадена функция е декларирана като виртуална в базовия клас, тя ще бъде виртуална във всички производни класове.
Но досега не сме обсъждали как точно виртуалните функции играят роля при идентифицирането на правилната функция, която трябва да бъде обвързана, или с други думи, колко късно свързване всъщност се случва.
Виртуалната функция е обвързана с тялото на функцията точно по време на изпълнение, като използва концепцията за виртуална таблица (VTABLE) и скрит указател, наречен _vptr.
И двете концепции са вътрешно изпълнение и не могат да се използват директно от програмата.
Работа на виртуална маса и _vptr
Първо, нека разберем какво е виртуална таблица (VTABLE).
Компилаторът по време на компилация настройва по една VTABLE за клас с виртуални функции, както и за класовете, получени от класове с виртуални функции.
VTABLE съдържа записи, които са указатели на функции към виртуалните функции, които могат да бъдат извикани от обектите на класа. За всяка виртуална функция има един запис на указател на функция.
В случай на чисто виртуални функции, този запис е NULL. (Това е причината, поради която не можем да създадем екземпляр на абстрактния клас).
Следващият обект, _vptr, който се нарича vtable указател, е скрит указател, който компилаторът добавя към базовия клас. Този _vptr сочи към vtable на класа. Всички класове, получени от този основен клас, наследяват _vptr.
Всеки обект от клас, съдържащ виртуалните функции, вътрешно съхранява този _vptr и е прозрачен за потребителя. След това всяко повикване към виртуална функция с помощта на обект се разрешава с помощта на този _vptr.
Нека вземем пример, за да демонстрираме работата на vtable и _vtr.
#include using namespace std; class Base_virtual { public: virtual void function1_virtual() {cout<<'Base :: function1_virtual()
';}; virtual void function2_virtual() {cout<<'Base :: function2_virtual()
';}; virtual ~Base_virtual(){}; }; class Derived1_virtual: public Base_virtual { public: ~Derived1_virtual(){}; virtual void function1_virtual() { coutfunction2_virtual(); delete (b); return (0); }
Изход:
Производно1_virtual :: function1_virtual ()
База :: function2_virtual ()
В горната програма имаме основен клас с две виртуални функции и виртуален деструктор. Също така сме извлекли клас от базовия клас и в това; сме заменили само една виртуална функция. В основната функция производният указател на клас се присвоява на основния указател.
След това извикваме и двете виртуални функции, използвайки указател на основен клас. Виждаме, че заменената функция се извиква, когато е извикана, а не основната функция. Докато във втория случай, тъй като функцията не е заменена, се извиква функцията на базовия клас.
Сега нека видим как горната програма е представена вътрешно с помощта на vtable и _vptr.
Според предишното обяснение, тъй като има два класа с виртуални функции, ще имаме две vtables - по една за всеки клас. Също така _vptr ще присъства за базовия клас.

По-горе е показано изображението на това как ще бъде оформлението на vtable за горната програма. Vtable за базовия клас е ясен. В случая на производния клас се заменя само function1_virtual.
Следователно виждаме, че в производния клас vtable, указателят на функция за function1_virtual сочи към заменената функция в производния клас. От друга страна, показалеца на функция за function2_virtual сочи към функция в базовия клас.
По този начин в горната програма, когато на основния указател е присвоен обект на производен клас, основният указател сочи към _vptr на получения клас.
Така че, когато е направено извикването b-> function1_virtual (), се извиква function1_virtual от производния клас и когато се извършва извикването на функцията b-> function2_virtual (), тъй като този указател на функция сочи към функцията на базовия клас, функцията на базовия клас е наречен.
Чисти виртуални функции и абстрактен клас
Видяхме подробности за виртуалните функции в C ++ в предишния ни раздел. В C ++ можем също да дефинираме „ чиста виртуална функция ”, Което обикновено се приравнява на нула.
Чистата виртуална функция е декларирана, както е показано по-долу.
virtual return_type function_name(arg list) = 0;
Класът, който има поне една чиста виртуална функция, която се нарича „ абстрактен клас ”. Никога не можем да създадем екземпляр на абстрактния клас, т.е.не можем да създадем обект от абстрактния клас.
Това е така, защото знаем, че се прави запис за всяка виртуална функция в VTABLE (виртуална таблица). Но в случай на чиста виртуална функция, този запис е без никакъв адрес, което го прави непълен. Така че компилаторът не позволява създаването на обект за класа с непълно въвеждане на VTABLE.
Това е причината, поради която не можем да създадем екземпляр на абстрактен клас.
Примерът по-долу ще демонстрира чиста виртуална функция, както и абстрактния клас.
#include using namespace std; class Base_abstract { public: virtual void print() = 0; // Pure Virtual Function }; class Derived_class:public Base_abstract { public: void print() { cout <<'Overriding pure virtual function in derived class
'; } }; int main() { // Base obj; //Compile Time Error Base_abstract *b; Derived_class d; b = &d; b->print(); }
Изход:
Заменяне на чиста виртуална функция в производния клас
В горната програма имаме клас, дефиниран като Base_abstract, който съдържа чиста виртуална функция, която го прави абстрактен клас. След това извличаме клас “Derived_class” от Base_abstract и заместваме чистата виртуална функция за печат в него.
В основната функция не се коментира първият ред. Това е така, защото ако го коментираме, компилаторът ще даде грешка, тъй като не можем да създадем обект за абстрактен клас.
Но вторият ред нататък кодът работи. Можем успешно да създадем указател на основен клас и след това да му присвоим обект на производен клас. След това извикваме функция за печат, която извежда съдържанието на функцията за печат, заменено в производния клас.
Нека изброим накратко някои характеристики на абстрактния клас:
- Не можем да създадем екземпляр на абстрактен клас.
- Абстрактният клас съдържа поне една чиста виртуална функция.
- Въпреки че не можем да създадем екземпляр на абстрактен клас, винаги можем да създадем указатели или препратки към този клас.
- Абстрактният клас може да има някаква реализация като свойства и методи, заедно с чисти виртуални функции.
- Когато извличаме клас от абстрактния клас, производният клас трябва да замени всички чисти виртуални функции в абстрактния клас. Ако не е успял да го направи, тогава извлеченият клас също ще бъде абстрактен клас.
Виртуални деструктори
Деструкторите от класа могат да бъдат декларирани като виртуални. Винаги, когато правим upcast, т.е.присвояване на производен обект на клас към указател на основен клас, обикновените деструктори могат да дадат неприемливи резултати.
Например,помислете за следващото обновяване на обикновения деструктор.
#include using namespace std; class Base { public: ~Base() { cout << 'Base Class:: Destructor
'; } }; class Derived:public Base { public: ~Derived() { cout<< 'Derived class:: Destructor
'; } }; int main() { Base* b = new Derived; // Upcasting delete b; }
Изход:
Основен клас :: Деструктор
В горната програма имаме наследен производен клас от базовия клас. Основно, ние присвояваме обект от извлечения клас на указател на основен клас.
В идеалния случай деструкторът, който се извиква при извикване на “delete b”, трябва да е този на производен клас, но от изхода можем да видим, че деструкторът на базовия клас се извиква като указател на базовия клас към това.
Поради това дерикторът на производен клас не се извиква и производният обект на клас остава непокътнат, което води до изтичане на памет. Решението за това е да се направи конструктор на основен клас виртуален, така че указателят на обекта да сочи към правилния деструктор и да се извършва правилното унищожаване на обектите.
Използването на виртуален деструктор е показано в примера по-долу.
#include using namespace std; class Base { public: virtual ~Base() { cout << 'Base Class:: Destructor
'; } }; class Derived:public Base { public: ~Derived() { cout<< 'Derived class:: Destructor
'; } }; int main() { Base* b = new Derived; // Upcasting delete b; }
Изход:
Производен клас :: Деструктор
Основен клас :: Деструктор
Това е същата програма като предишната програма, с изключение на това, че сме добавили виртуална ключова дума пред деструктора на базовия клас. Правейки деструктора на основен клас виртуален, постигнахме желания резултат.
Можем да видим, че когато присвоим обект на производен клас на показалеца на базовия клас и след това изтрием указателя на базовия клас, деструкторите се извикват в обратния ред на създаването на обекта. Това означава, че първо се извиква деструкторът на производен клас и обектът се унищожава и след това обектът на базовия клас се унищожава.
Забележка: В C ++ конструкторите никога не могат да бъдат виртуални, тъй като конструкторите участват в конструирането и инициализирането на обектите. Следователно имаме нужда всички конструктори да бъдат изпълнени изцяло.
въпрос и отговор за интервю в мрежа pdf
Заключение
Полиморфизмът по време на изпълнение е реализиран с използване на метода, заместващ. Това работи добре, когато извикаме методите със съответните им обекти. Но когато имаме указател на основен клас и извикваме заменени методи, използвайки указателя на базовия клас, сочещ към производни обекти на класа, възникват неочаквани резултати поради статично свързване.
За да преодолеем това, използваме концепцията за виртуални функции. С вътрешното представяне на vtables и _vptr, виртуалните функции ни помагат точно да извикаме желаните функции. В този урок видяхме подробно за полиморфизма по време на изпълнение, използван в C ++.
С това завършваме нашите уроци по обектно-ориентирано програмиране в C ++. Надяваме се, че този урок ще бъде полезен за по-добро и задълбочено разбиране на обектно-ориентираните концепции за програмиране в C ++.
=> Посетете тук, за да научите C ++ от нулата.
Препоръчително четене
- Полиморфизъм в C ++
- Наследяване в C ++
- Приятелски функции в C ++
- Класове и обекти в C ++
- Използване на Selenium Select Class за работа с падащи елементи на уеб страница - Урок № 13 за Selenium
- Урок за основната функция на Python с практически примери
- Java виртуална машина: Как JVM помага при стартирането на Java приложение
- Как да настроите LoadRunner VuGen Script файлове и настройки по време на работа