Programování v C++ VI Konstruktory, destruktory a dědičnost
Konstruktory a dědičnost I když jsme se bavili o dědičnosti, trochu jsme zapomněli na konstruktory to se ale nevyplácí, vzpomeňte si, jak důležitý je konstruktor konstruktor se volá vždy při inicializaci objektu a slouží k uvedení členských proměnných do rozumného stavu konstruktor také často alokuje zdroje objektu (paměť, atd.) je dokonce tak důležitý, že pokud ho vynecháme, přidá překladač automaticky konstruktor defaultní a ten nemá žádné parametry a ani nic nedělá (alespoň si to zatím myslíme...)
Konstruktory a dědičnost II pokud je třída potomkem jiné třídy (jiných tříd), počítá každá její funkce s tím, že veškerá funkčnost rodičů je jí již k dispozici a konstruktor není vyjímkou, i konstruktor potomka očekáva, že předek již existuje. potomek a předek jsou sice defacto jeden a ten samý objekt, to, že předek již existuje můžeme chápat spíš tak, že konstruktor potomka očekává, že ta část, odpovídající funkčnosti předka je již ve správném a smysluplném stavu tedy, že již konstruktor předka proběhl ještě před vstupem do těla konstruktoru potomka!!
Konstruktory a dědičnost III pokud existuje defaultní konstruktor (tedy konstruktor bez argumentů), může ho překladač zavolat sám proto zatím naše příklady fungovaly, defaultní konstruktory volal automaticky překladač a právě proto překladač automaticky dodává implicitní defaultní konstruktor, i když ho nedefinujeme dělá to, aby měl místo, na kterém může konstruktory předků volat i když defaultní konstruktor v potomkovi nemusí nic dělat, vždy volá konstruktory předků (pokud existují)
Konstruktory a dědičnost IV co ale dělat, když nemáme defaultní konstruktor k dispozici? pak už překladač nemůže konstruktor zavolat, protože neví, jak inicializovat jeho argumenty o zavolání konstruktorů předků se tedy musíme postarat my a musíme to udělat ještě před vstupem do těla funkce jediná možná cesta je tedy napsat volání konstruktorů předků za seznam argumentů konstruktoru a před otevírací závorku volání konstruktorů předků ale nepatří do hlavičky funkce a proto se zapisují až do implementačního souboru (pokud konstruktor není inlinován, pak je v hlavičce i definice)
Konstruktory a dědičnost V class A { } ; public: A(int i) { cout << Konstruktor A: << i << endl } class B:public A { } public: B():A(5) { } // Defaultni konstruktor B je ted nutne napsat B(int i):a(i+2) { cout << Konstruktor B << I << endl; }
Konstruktory a dědičnost VI protože třída A nemá defaultní konstruktor, třída B nemůže mít konstruktor implicitní protože překladač neví jak volat parametrizovaný konstruktor A musíme ho zavolat sami, což dělá první konstruktor v B druhý konstruktor pak má parametr a volá konstruktor A s tímto upraveným parametrem parametry pro konstruktor předka totiž můžete upravit v rámci jeho volání výrazy (I voláním funkcí (ne však vlastních, metod, ty ještě nejsou dostupné)
Virtuální Destruktory I v souvislosti s dědičností jsme se taky nebavili vůbec o destruktorech což je taky chyba... destruktory jsou stejně důležité jako konstruktory starají se o uvolnění zdrojů objektu, v C++ typicky paměti stejně jako se musí zavolat před konstruktorem potomka všechny konstruktory předků, je situace u destruktoru obdobná všechno je ale obráceně nejdřív se zničí potomek a pak se zničí všichni jeho předkové obojí rekurzivně ;-)
Virtuální destruktory II problém ale nastane, když používáme polymorfismus pokud totiž destruktor není virtuální, zavolání destruktoru polymorfního předka nezpůsobí zavolání destruktoru v potomkovi což vede k segfaultům a dalším méně příjemným věcem, kterých se všichni snažíme vyvarovat zásada by opět měla být: pokud si nejste jisti, že nebudete potřebovat polymorfismus pro destruktory, mějte destruktor vždy virtuální
Virtuální destruktory III class A { public: virtual ~A() { cout << killing A << endl; } } ; class B:public A { public: virtual ~B() { cout << killing B << endl; } } ; void killobject(a* x) { delete x; } killobject(new B());
Konstruktory a destruktory všimněte si, že překladač automaticky rekurzivně volá konstruktory a destruktory předchůdců toto chování je specifické pouze pro konstruktory a destruktory jen tady je totiž nezbytně nutné všude jinde si je musíte vynutit: void overridenmethod() { // do my stuff Parent::overridenMethod(); }
A zase je tu konec... dneska rychle...