runtime polymorphism c
Podrobná studie runtime polymorfismu v C ++.
Runtime polymorfismus je také známý jako dynamický polymorfismus nebo pozdní vazba. V běhovém polymorfismu je volání funkce vyřešeno za běhu.
Naproti tomu pro kompilaci času nebo statického polymorfismu kompilátor odvodí objekt za běhu a poté rozhodne, které volání funkce se má vázat na objekt. V C ++ je runtime polymorfismus implementován pomocí přepsání metody.
V tomto kurzu podrobně prozkoumáme runtime polymorfismus.
=> Zkontrolujte VŠECHNY výukové programy pro C ++ zde.
Co se naučíte:
- Přepsání funkce
- Virtuální funkce
- Práce s virtuální tabulkou a _vptr
- Čisté virtuální funkce a abstraktní třída
- Virtuální destruktory
- Závěr
- Doporučené čtení
Přepsání funkce
Přepsání funkce je mechanismus, pomocí kterého je funkce definovaná v základní třídě opět definována v odvozené třídě. V tomto případě říkáme, že funkce je v odvozené třídě přepsána.
Měli bychom si pamatovat, že přepsání funkce nelze provést v rámci třídy. Funkce je přepsána pouze v odvozené třídě. Proto by měla být přítomna dědičnost pro přepsání funkce.
Druhá věc je, že funkce ze základní třídy, kterou přepisujeme, by měla mít stejný podpis nebo prototyp, tj. Měla by mít stejný název, stejný návratový typ a stejný seznam argumentů.
Podívejme se na příklad, který ukazuje přepsání metody.
#include using namespace std; class Base { public: void show_val() { cout << 'Class::Base'< Výstup:
Třída :: Základna
Třída :: Odvozeno
Ve výše uvedeném programu máme základní třídu a odvozenou třídu. V základní třídě máme funkci show_val, která je v odvozené třídě přepsána. V hlavní funkci vytvoříme objekt každé třídy Base a Derived a s každým objektem zavoláme funkci show_val. Produkuje požadovaný výstup.
Výše uvedená vazba funkcí pomocí objektů každé třídy je příkladem statické vazby.
Nyní se podívejme, co se stane, když použijeme ukazatel základní třídy a jako jeho obsah přiřadíme odvozené objekty třídy.
Ukázkový program je uveden níže:
#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 }
Výstup:
Třída :: Základna
Nyní vidíme, že výstup je „Class :: Base“. Takže bez ohledu na to, jaký typ objektu drží základní ukazatel, program vypíše obsah funkce třídy, jejíž základní ukazatel je typ. V tomto případě se provede také statické propojení.
Abychom vytvořili výstup základního ukazatele, správný obsah a správné propojení, jdeme na dynamickou vazbu funkcí. Toho je dosaženo pomocí mechanismu virtuálních funkcí, který je vysvětlen v následující části.
Virtuální funkce
Protože přepsaná funkce by měla být dynamicky vázána na tělo funkce, uděláme funkci základní třídy virtuální pomocí klíčového slova „virtual“. Tato virtuální funkce je funkce, která je přepsána v odvozené třídě a kompilátor provádí pro tuto funkci pozdní nebo dynamickou vazbu.
Nyní upravme výše uvedený program tak, aby obsahoval virtuální klíčové slovo, a to následovně:
#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 }
Výstup:
Třída :: Odvozeno
Takže ve výše uvedené definici třídy Base jsme vytvořili funkci show_val jako „virtuální“. Protože je funkce základní třídy virtuální, když přiřadíme objekt odvozené třídy ukazateli základní třídy a zavoláme funkci show_val, k vazbě dojde za běhu.
Protože ukazatel základní třídy obsahuje objekt odvozené třídy, tělo funkce show_val v odvozené třídě je tedy vázáno na funkci show_val a tedy na výstup.
V C ++ může být přepsaná funkce v odvozené třídě také soukromá. Kompilátor kontroluje pouze typ objektu v době kompilace a váže funkci za běhu, proto nedělá žádný rozdíl, i když je funkce veřejná nebo soukromá.
Všimněte si, že pokud je funkce deklarována jako virtuální v základní třídě, bude virtuální ve všech odvozených třídách.
Ale dosud jsme nediskutovali o tom, jak přesně virtuální funkce hrají roli při identifikaci správné funkce, která má být vázána, nebo jinými slovy, jak se ve skutečnosti stane pozdní vazba.
Virtuální funkce je za běhu svázána s tělem funkce přesně pomocí konceptu virtuální tabulka (VTABLE) a zavolal skrytý ukazatel _vptr.
Oba tyto koncepty jsou interní implementací a program je nemůže přímo použít.
Práce s virtuální tabulkou a _vptr
Nejprve pochopme, co je virtuální tabulka (VTABLE).
Kompilátor v době kompilace nastaví po jednom VTABLE pro třídu s virtuálními funkcemi i pro třídy, které jsou odvozeny od tříd s virtuálními funkcemi.
VTABLE obsahuje položky, které jsou ukazateli funkcí na virtuální funkce, které lze volat objekty třídy. Pro každou virtuální funkci existuje jeden záznam ukazatele funkce.
který je nejlepší youtube downloader?
V případě čistě virtuálních funkcí je tato položka NULL. (To je důvod, proč nemůžeme vytvořit instanci abstraktní třídy).
Další entita, _vptr, která se nazývá vtable ukazatel, je skrytý ukazatel, který kompilátor přidá do základní třídy. Tento _vptr ukazuje na vtable třídy. Všechny třídy odvozené z této základní třídy dědí _vptr.
Každý objekt třídy obsahující virtuální funkce interně ukládá tento _vptr a je pro uživatele transparentní. Každé volání virtuální funkce pomocí objektu je poté vyřešeno pomocí tohoto _vptr.
Vezměme si příklad, který předvede fungování vtable a _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); }
Výstup:
Derived1_virtual :: function1_virtual ()
Base :: function2_virtual ()
Ve výše uvedeném programu máme základní třídu se dvěma virtuálními funkcemi a virtuálním destruktorem. Také jsme odvodili třídu od základní třídy a v tom; přepsali jsme pouze jednu virtuální funkci. V hlavní funkci je ukazatel odvozené třídy přiřazen k základnímu ukazateli.
Potom zavoláme obě virtuální funkce pomocí ukazatele základní třídy. Vidíme, že přepsaná funkce se volá, když se volá, a ne základní funkce. Zatímco v druhém případě, protože funkce není přepsána, je volána funkce základní třídy.
Nyní se podívejme, jak je výše uvedený program interně reprezentován pomocí vtable a _vptr.
Podle dřívějšího vysvětlení, protože existují dvě třídy s virtuálními funkcemi, budeme mít dvě vtables - jednu pro každou třídu. Také _vptr bude přítomen pro základní třídu.

Nahoře je znázorněno obrazové znázornění toho, jak bude rozložení vtable pro výše uvedený program. Vtable pro základní třídu je přímočarý. V případě odvozené třídy je přepsána pouze funkce1_virtual.
Proto vidíme, že v odvozené třídě vtable ukazuje ukazatel funkce pro funkci1_virtual na přepsanou funkci v odvozené třídě. Na druhou stranu ukazatel funkce pro function2_virtual ukazuje na funkci v základní třídě.
Takže ve výše uvedeném programu, když je základnímu ukazateli přiřazen objekt odvozené třídy, ukazuje základní ukazatel na _vptr odvozené třídy.
Takže když je provedeno volání b-> function1_virtual (), je vyvolána funkce1_virtual z odvozené třídy a když je provedeno volání funkce b-> function2_virtual (), protože tento ukazatel funkce ukazuje na funkci základní třídy, funkce základní třídy je nazýván.
Čisté virtuální funkce a abstraktní třída
V naší předchozí části jsme viděli podrobnosti o virtuálních funkcích v C ++. V C ++ můžeme také definovat „ čistá virtuální funkce „To se obvykle rovná nule.
Čistá virtuální funkce je deklarována, jak je znázorněno níže.
virtual return_type function_name(arg list) = 0;
Třída, která má alespoň jednu čistě virtuální funkci, která se nazývá „ abstraktní třída “. Nikdy nemůžeme vytvořit instanci abstraktní třídy, tj. Nemůžeme vytvořit objekt abstraktní třídy.
Je to proto, že víme, že pro každou virtuální funkci ve VTABLE (virtuální tabulka) je vytvořen záznam. Ale v případě čistě virtuální funkce je tato položka bez jakékoli adresy, což ji činí neúplnou. Kompilátor tedy neumožňuje vytvořit objekt pro třídu s neúplným záznamem VTABLE.
To je důvod, proč nemůžeme vytvořit instanci abstraktní třídy.
Níže uvedený příklad předvede čistou virtuální funkci i třídu Abstract.
#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(); }
Výstup:
Přepsání čisté virtuální funkce v odvozené třídě
Ve výše uvedeném programu máme třídu definovanou jako Base_abstract, která obsahuje čistou virtuální funkci, která z ní dělá abstraktní třídu. Potom odvozíme třídu „Derived_class“ z Base_abstract a přepíšeme v ní čistý virtuální funkční tisk.
V hlavní funkci není komentován první řádek. Je to proto, že pokud to odkomentujeme, kompilátor dá chybu, protože nemůžeme vytvořit objekt pro abstraktní třídu.
Ale druhý řádek dále funguje kód. Můžeme úspěšně vytvořit ukazatel základní třídy a poté k němu přiřadíme odvozený objekt třídy. Dále zavoláme tiskovou funkci, která vypíše obsah tiskové funkce přepsané v odvozené třídě.
Uveďme ve zkratce některé charakteristiky abstraktní třídy:
- Nemůžeme vytvořit instanci abstraktní třídy.
- Abstraktní třída obsahuje alespoň jednu čistě virtuální funkci.
- Ačkoli nemůžeme vytvořit instanci abstraktní třídy, můžeme vždy vytvořit ukazatele nebo odkazy na tuto třídu.
- Abstraktní třída může mít nějakou implementaci, jako jsou vlastnosti a metody, spolu s čistě virtuálními funkcemi.
- Když odvozujeme třídu z abstraktní třídy, měla by odvozená třída přepsat všechny čisté virtuální funkce v abstraktní třídě. Pokud se to nepodařilo, bude odvozená třída také abstraktní třídou.
Virtuální destruktory
Destruktory třídy lze deklarovat jako virtuální. Kdykoli uděláme upcast, tj. Přiřadíme objekt odvozené třídy ukazateli základní třídy, obyčejné destruktory mohou způsobit nepřijatelné výsledky.
Například,zvažte následující upcasting obyčejného destruktoru.
#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; }
Výstup:
Základní třída :: Destruktor
Ve výše uvedeném programu máme zděděnou odvozenou třídu ze základní třídy. V hlavní části přiřadíme objekt odvozené třídy ukazateli základní třídy.
V ideálním případě by měl být destruktor, který se volá, když se volá „delete b“, odvozeným třídou, ale z výstupu vidíme, že destruktor základní třídy se nazývá jako ukazatel základní třídy.
rozdíl mezi zdravým rozumem a kouřovým testem
Z tohoto důvodu není volán destruktor odvozené třídy a objekt odvozené třídy zůstává nedotčen, což má za následek nevracení paměti. Řešením je vytvořit virtuální konstruktor základní třídy tak, aby ukazatel objektu směřoval k opravě destruktoru a řádnému zničení objektů.
Použití virtuálního destruktoru je uvedeno v následujícím příkladu.
#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; }
Výstup:
Odvozená třída :: Destruktor
Základní třída :: Destruktor
Toto je stejný program jako předchozí program, kromě toho, že jsme přidali virtuální klíčové slovo před destruktor základní třídy. Vytvořením virtuálního destruktoru základní třídy jsme dosáhli požadovaného výstupu.
Vidíme, že když přiřadíme objekt odvozené třídy k ukazateli základní třídy a potom odstraníme ukazatel základní třídy, destruktory jsou volány v opačném pořadí při vytváření objektu. To znamená, že se nejprve zavolá destruktor odvozené třídy a objekt se zničí a poté se zničí objekt základní třídy.
Poznámka: V C ++ konstruktory nikdy nemohou být virtuální, protože konstruktory se podílejí na konstrukci a inicializaci objektů. Proto potřebujeme, aby byly všechny konstruktory provedeny úplně.
Závěr
Runtime polymorfismus je implementován pomocí přepsání metody. To funguje dobře, když voláme metody s jejich příslušnými objekty. Ale když máme ukazatel základní třídy a voláme přepsané metody pomocí ukazatele základní třídy ukazující na objekty odvozené třídy, dojde k neočekávaným výsledkům kvůli statickému propojení.
Abychom to překonali, používáme koncept virtuálních funkcí. S interní reprezentací vtables a _vptr nám virtuální funkce pomáhají přesně volat požadované funkce. V tomto kurzu jsme podrobně viděli runtime polymorfismus používaný v C ++.
S tímto uzavíráme naše výukové programy o objektově orientovaném programování v C ++. Doufáme, že tento výukový program vám pomůže získat lepší a důkladnější pochopení konceptů objektově orientovaného programování v C ++.
=> Navštivte zde a dozvíte se C ++ od nuly.
Doporučené čtení
- Polymorfismus v C ++
- Dědičnost v C ++
- Funkce přátel v C ++
- Třídy a objekty v C ++
- Použití třídy Select Selenium pro zpracování prvků rozevíracího seznamu na webové stránce - Selenium Tutorial # 13
- Výukový program pro hlavní funkce Pythonu s praktickými příklady
- Virtuální stroj Java: Jak JVM pomáhá při spouštění aplikace Java
- Jak nastavit soubory skriptu LoadRunner VuGen a nastavení běhového prostředí