Table of Contents
Tabulka virtuálních metod
Třídy, které mají virtuální metody, obsahují ukazatel na tabulku virtuálních metod.
Tabulka má pro každou virtuální metodu jednu řádku s ukazatelem na funkci implementující danu metodu.
Každé virtuální metodě překladač přiřadí číslo řádky v tabulce virtuálních metod.
Pokud vytvoříme více instancí téhož typu obsahují všechny instance ukzatel na stále stejnou tabulku virtuálních metod.
( A pokud máme více virtuálních metod, ušetříme místo v paměti, jednotlivé instance obsahují jeden ukazatel na tabulku místo několika ukazatelů na funkce, které by odpovídaly implementaci z předešlé kapitoly.)
Při volání virtuální metody se v instanci nalezne ukazatel na tabulku, v tabulce použije řádku odpovídající dané metodě a zde nalezene ukazatel na funkci, kterou zavolá.
( Při volání virtuální metod nesmí být ukazetel na instanci roven nullptr, jinak by nešlo nalézt tabulku virtuálních metod. )
V následujícím příkladu jsou dvě třídy a několik virtuálních metod.
class Basic { public: int x, y; public : virtual void print () { cout << "Basic (" << x << ", " << y << ")" << endl; } virtual int distance () { return sqrt (x*x +y*y); } virtual void scale (int c) { x = x * c; y *=c; } }; class Component : public Basic { public: int z; public : virtual void print () { cout << "Component (" << x << ", " << y << "," << z << ")" << endl; } virtual int distance () { return sqrt (x*x +y*y + z*z); } virtual void move (int dx, int dy, int dz) { x += dx; y += dy; z += dz; } };
- Vytvoříme instanci typu Basic uložíme ji do proměnné b.
- Další instanci typu Component uložíme do proměnné c.
- Třída Component je odvozená z třídy Basic a tak můžeme ukazatel na druhou instanci uložit také do proměnné u typu ukazatel na Basic
Basic * b = new Basic; Component * c = new Component; Basic * u = c;
Proměnné x, y a z jsou poněkud nestandardně veřejně přístupné (public:)
K těmto proměnným můžeme přistupovat pomocí b→x, u→y nebo c→z
K proměnné z se nedostaneme pomocí ukazatele na Basic, tedy u→z nelze použít.
- Třída Basic má tabulku virtuálních metod se třemi řádkami, odpovídajícími metodám print, distance a scale
- Třída Component má tabulku se čtyřmi řádky, přibyla metoda move
- První dvě řádky obsahují ukazatele na předefinované print a distance ze třídy Component
- Ve třetí řádce zůstává scale ze třídy Basic, neboť v odvozené třídě nebyla předefinována
- Přibyla čtvrtá řádka pro move
- Metodu move můžeme zavolat pomocí c→move (1, 2, 3)
- Nelze použít u→move, neboť u je typu ukazatel na Basic a třída Basic nezná metodu move.
Vynechání některých klíčových slov virtual v odvozené třídě
Pokud vynecháme klíčové slovo virtual u metod print a distance ve třídě Component, program se bude chovat stejně, metody jsou již deklarovány jako virtální ve tříde Basic.
Vynechání některých klíčových slov virtual v základní třídě
Pokud vynecháme klíčová slova virtual u metod print a distance ve třídě Basic
- c→print() bude stále volat Component::print neboť c je typu ukazatel na Component
- u→print() bude stále volat Basic::print, protože u je typu ukazatel na Basic a print není virtuální metoda
- virtual print () až ve třídě Component to nijak neovlivní
Vynechání všech klíčových slov virtual v základní třídě
Pokud vynecháme klíčová slova virtual u všech metod ve třídě Basic,
třída nebude obsahovat ukazatel na tabulku virtuálních metod
Pokud odvozená třída Component stále bude mít virtuální metody,
překladač nejspíše ve třídě komponent nejprve umístí proměnné x a y, aby třída Component začínanala stejně třída Basic,
a ukazatele na instance odvozené třídy Component se daly přiřadit i do proměnných typu ukazatele na Basic (např. u = c;)
Za proměnné x a y nejspíše bude umístěn ukazatel na tabulku virtuálních metod a až za ním bude proměnná z.
Přetypování dynamic_cast
Proměnnou typu ukazatel na základní třídu nemůžeme přiřadit do proměnné typu ukazatel na odvozenou třídu.
Basic * b = new Basic; Component * v = b;
Je zřejmé, že objekt typu Basic nemá proměnnou z a metodu move, a proto nelze přiřadit v = b;
Pokud v proměnné typu Basic* byl uložen objekt typu Component, potom by bylo možné objekt uložit do proměné typu Component*.
Ovšem překladač nám stejně přiřazení nedovolí.
Z hlediska překladače je přířazení ukazatele na Basic do ukazatele na Component potenciálně nebezpečné.
Item * c = new Component; Basic * u = c; Component * v = u; /* překladač ohlásí chybu */
Konstrukce dynamic_cast umožňuje přetypování ukazatele na základní třídu na ukazatel na odvozenou třídu
Basic * u; Component * v = dynamic_cast < Component * > (u);
Pokud parametr v kulatých závorkách za běhu programu nebude skutečně typu uvedeného v < > závorkách, bude výsledkem nullptr