Fotografický magazín "iZIN IDIF" každý týden ve Vašem e-mailu.
Co nového ve světě fotografie!
Zadejte Vaši e-mailovou adresu:
Kamarád fotí rád?
Přihlas ho k odběru fotomagazínu!
Zadejte e-mailovou adresu kamaráda:
C/C++
Časná versus pozdní vazba - úvod do polymorfismu v C++
19. února 2001, 00.00 | Jaký je rozdíl mezi časnou a pozdní vazbou? Dále si ukážeme jak a proč používat klíčové slovo virtual.
Všechny metody, které jsem ve svých článcích doposud používal byly volány tak zvanou časnou vazbou. Tedy překladač v době překladu přesně věděl jaký podprogram (metoda) bude kdy vyvolán. Například v mém minulém článku "Jednoduchá dědičnost v C++" jsem v jednom ukázkovém programu uvedl řádek v1->nastavKola(4);. Pro překladač je jednoznačné, který podprogram se má vyvolat. Metoda Vozidlo::nastavKola(int) bude vyvolána i když v1 bude instance třídy Vozidlo, nebo instance třídy Nákladní vozidlo. V takovém případě říkáme, že metoda je volána časnou vazbou. Pojem "časná vazba" asi proto, že překladač zná adresu podprogramu na který má předat řízení včas (V době překladu.) Může ale nastat případ (nastává velmi často), kdy budeme potřebovat, aby metoda pro podtřídu vykonávala jinou činnost, než stejná metoda u nadtřídy. Tedy vlastně budeme potřebovat přepsat tělo metody v podtřídě. Jednoduše metodu přepsat sice jde, ale není to dobré. Uvedu příklad: (Metody void casna() jsou volány časnou vazbou.)
|
Po spuštění zjistíte, že řádek problem->casna(); vyvolá metodu void Nadtrida::casna() což jsme nechtěli, protože ukazatel problém je sice ukazatel typu Nadtřída, ale ve skutečnosti ukazuje na instanci třídy Podtřída. Na místo, kde byl očekáván předek byl dosazen potomek - v OOP běžná a často používaná konstrukce.
Podívejme se, jak překladač postupoval při překladu řádku problem->casna(); . Nejprve zjistil typ ukazatele problém. Ukazatel je deklarován jako Nadtrida *problem. Zjistil si, jestli třída Nadtřída (Případně některý její předek - v našem případě žádný není.) má definovanou metodu void casna();. Protože má, rozhodl již v době překladu o tom, že se bude volat void Nadtrida::casna();. Kdyby neměla, nahlásil by chybu. Fakt, že ukazatel problém může ukazovat na potomka nyní překladač nezajímalo. Proto abychom na řádku problem->casna(); volali metodu void casna() podle toho, na jakou instanci ukazatel problém ukazuje, musíme použít pozdní vazbu.
Klíčové slovo virtual
Klíčové slovo virtual před deklarací metody překladači přikazuje použít tak zvanou pozdní vazbu při volání dané metody. Zkuste do mého příkladu mezi veřejné metody třídy Nadtřída vepsat řádek virtual void pozdni(); a mezi veřejné metody třídy Podtřída vepište řádek virtual void pozdni();. Potom do zdrojového textu vepište těla těchto metod:
|
A na konec funkce main (ale samozřejmě před řádek return 0;)dopište řádky:
|
O tom která metoda ( void Nadtrida::pozdni(); , nebo void Podtrida::pozdni(); ) ve skutečnosti bude volána se rozhoduje až při běhu programu podle toho, na jakou instanci je volána, ne v době kompilace.
Jak je možné, že to funguje?Jak jsem ve svých předchozích článcích naznačil metody s časnou vazbou jsou překládány jako "obyčejné" céčkovské funkce, kde je přidán jako 1. parametr this - implicitní parametr, který ukazuje na instanci, pro kterou je metoda vyvolána. Rozdíl mezi "obyčejnou" funkcí a metodou volanou časnou vazbou je jen v tom, že překladač u metody kontroluje, zda je volána skutečně na správný objekt. Metody volané pozdní vazbou (virtuální metody) se překládají trochu jinak. Každá instance, která má alespoň jednu virtuální metodu má v sobě navíc ukazatel na tak zvanou tabulku virtuálních metod (TVM, někdy jsem také viděl zkratku VMT). Tento ukazatel je pro programátora nepřístupný (Alespoň ne korektní cestou.) a ukazuje na tabulku, ve které jsou uloženy adresy virtuálních metod. O tom, že v instanci je jeden ukazatel navíc se můžete lehce přesvědčit pomocí sizeof. Řádek problem->casna(); bude přeložen ne jako jednoduché zavolání funkce, ale jako vyvolání 1. virtuální metody (metody, jejíž adresa je ve TVM na 1. místě) která je ve TVM pro instanci na kterou ukazatel problém ukazuje. O správnou inicializaci ukazatele na TVM se postará překladač, který při vytváření každé instance (před zavoláním samotného konstruktoru) přidá kód pro inicializaci ukazatele na TVM. Programátor, který chce používat virtuální metody nemusí vlastně vůbec pojmy jako TVM znát, prostě stačí, že to funguje jak má. Uvedl jsem je jen proto, aby byly jasné některé omezení:
Konstruktor virtuální být nemůže, ale destruktor virtuální být může a dokonce by i měl být. Alespoň v případě, že třída má jiné virtuální metody. V mém předchozím článku jsem upozornil na jedno zvláštní vyvolání "nesprávného" destruktoru. Problém byl v tom, že destruktor byl překládán časnou vazbou.
Někteří uživatelé Borland C++ Builderu si mohou všimnout jedné zajímavosti, která jako-by vyvrací mé tvrzení. Například třída TForm z knihovny VCL má konstruktor deklarovaný s klíčovým slovem virtual. Nejedná se v žádném případě o konstruktor volaný pozdní vazbou. Konstruktor volaný pozdní vazbou není ani v ANSI C++, ani nemůže být žádným rozšířením jazyka C++. Klíčové slovo virtual u konstruktorů některých tříd z VCL má jiný význam než pozdní vazba, a nemá s ANSI C++ nic společného.
Tolik pro vysvětlení rozdílu mezi časnou a pozdní vazbou, příště dokončím téma polymorfismu. Poté v dalším článku se vrátím zpět k dědičnosti, tentokráte vícenásobné, což je téma na 2 - 3 články.
Obsah seriálu (více o seriálu):
- Základy OOP v C++: Od C k C++
- Základní pojmy objektově orientovaného programování
- Vytváření tříd, instance třídy, zasílání zpráv v C++
- Vytváření instancí - konstruktory, destruktory
- Kopírovací konstruktor v C++
- Jednoduchá dědičnost v C++
- Časná versus pozdní vazba - úvod do polymorfismu v C++
- Polymorfismus - dokončení
- Vícenásobná dědičnost v C++
- Vícenásobná dědičnost v C++ - opakovaná dědičnost
- Vícenásobná dědičnost v C++ - volání konstruktorů a destruktorů
- Přetěžování operátorů v C++ 1.díl
- Přetěžování operátorů v C++ 2. díl
- Vstupní a výstupní operace pomocí datových proudů v C++
- Přetěžování operátorů << a >> pro datové proudy v C++
- Neformátovaný vstup a výstup v C++
- Paměťové proudy v C++
- Prostory jmen v C++
- Řetězce v C++
- Výjimky v C++
- Výjimky v C++ - výjimky tvoří dědičnou hierarchii
- Výjimky v C++ - dokončení
- Dynamická identifikace typů v C++
- Přetypování v C++
- Problémy s typy při vícenásobné dědičnosti
- Šablony funkcí v C++
- Šablony datových typů v C++
- Vnitřní typy u parametrů šablon, vnořené šablony v C++
- Pole s libovolným intervalem indexování v C++
- Datové kontejnery v C++ - Úvod do STL
- Vector - datový kontejner v C++
- Iterátory v C++
- Šablona vector v C++ a iterátory
- Asociativní pole v C++
- Množina v C++
- Funkční objekty v C++
- Standardní funkční objekty v C++
- Úvod do standardních algoritmů v C++
- Kopírovací a přesouvací algoritmy v C++
- Vyhledávací algoritmy v C++
- Skenovací (prohlížecí) algoritmy v C++
- Transformační algoritmy v C++
- Řadící algoritmy v C++
- Halda v C++
- Standardní algoritmy v C++ - dokončení
- Automatické ukazatele v C++
- Inteligentní ukazatel - čítač referencí v C++
- Použití čítače referencí v C++
- Kopírování velkých objektů v C++
- Řízené kopírování prvků v poli v C++
- Dokončení seriálu objektově orientované programování v C++
-
25. listopadu 2012
-
30. srpna 2002
-
10. října 2002
-
4. listopadu 2002
-
12. září 2002
-
25. listopadu 2012
-
28. července 1998
-
31. července 1998
-
28. srpna 1998
-
6. prosince 2000
-
27. prosince 2007
-
4. května 2007