Image
16.6.2016 0 Comments

C++ / Špeciálne členské funkcie / 18. časť

Späť na úvod >> Späť na programovanie >> Späť na seriál

Tak tu máme opäť leto. Keďže priveľké namáhanie mozgových závitov pri nadmerných teplotách môže viesť k neočakávanej reakcii (inak povedané, horúčavy a nadmerné štúdium dokopy veľmi nejdú), budeme sa dnes zaoberať pomerne jednoduchou témou konštruktorov a deštruktorov tried v C++. Keby u vás došlo k spomínanej reakcii, v prvom rade odlepte oči od PC REVUE a v druhom rade choďte niekam von, do prírody, prinajhoršom na nejaké to pivo :–).

Už v predchádzajúcich častiach sme sa zmieňovali o dvoch členských funkciách so zvláštnymi menami a špecifickým postavením. Tušíte správne, ide o konštruktor a deštruktor. Teraz si o nich povieme viac.

Konštruktor

Konštruktor je členská funkcia, ktorá sa poväčšine stará o inicializáciu údajových členov inštancie triedy. Vieme, že na rozdiel od nečlenských deklarácií nemôžeme inicializovať členy tried priamo v deklarácii. A priradovať zakaždým po vytvorení nového objektu explicitne jednotlivým členom východiskové hodnoty pomocou operátora priradenia, to by nás asi veľmi rýchlo omrzelo. A to už vôbec nehovoríme o prípadoch, keď by takáto „inicializácia“ nebola možná pre neprístupnosť privátnych údajových členov.

Z tohto dôvodu existuje v C++ možnosť doplniť do deklarácie triedy funkciu, ktorá požadovanú inicializáciu zrealizuje. Ako, to už je na nás. Konštruktor nikdy nevoláme explicitne, jeho vyvolanie má na starosti prekladač a dochádza k nemu vtedy, keď vzniká nový objekt danej triedy. Automatické objekty sú konštruované vždy - keď program vstúpi do bloku, v ktorom sú deklarované (napr. funkcia), statické objekty sa konštruujú len raz, pred spustením programu (globálne), resp. pri prvom prechode deklaráciou (lokálne). Objekty vytvárané dynamicky, pomocou operátora new, sú konštruované v rámci tohto operátora (samozrejme, až po pridelení pamäte).

Konštruktor sa od ostatných členských funkcií odlišuje v princípe dvoma spôsobmi: predovšetkým musí mať rovnaké meno ako trieda, ktorej je členom, a okrem toho nesmie mať deklarovanú návratovú hodnotu (ani ako typ void). Vďaka tomu nie je možné v tele konštruktora použiť príkaz return s argumentom. Čo sa týka zoznamu argumentov konštruktora, ten závisí len a len od našich potrieb. Pochopiteľne, v jednej triede môžeme mať viacero prekrytých konštruktorov, každý s iným zoznamom argumentov. Akým spôsobom zariadime, aby konštruktor dostal nami požadované argumenty, to si ukážeme neskôr na príkladoch.

V tele konštruktora môžeme bez problémov volať ostatné členské funkcie. Konštruktor nesmieme deklarovať s kľúčovým slovom const či volatile – kompilátor pri jeho volaní neberie ohľad na const/volatile deklaráciu inštancií. Konštruktor nesmie byť statický ani virtuálny (vysvetlíme neskôr).

Dva typy konštruktorov sa používajú častejšie ako ostatné, preto majú samostatné názvy. Prvým je implicitný konštruktor (default constructor), ktorý možno zavolať bez argumentov (to dosiahneme buď prázdnym zoznamom argumentov, alebo deklaráciou všetkých argumentov konštruktora ako implicitných). Takýto konštruktor sa uplatní pri deklarácii explicitne neinicializovaných objektov. Spomeňme si, že ak sme pri deklarácii premennej nejakého jednoduchého typu, napríklad int, vynechali inicializátor, vytvorená premenná obsahovala nejaký náhodný obsah, závislý od stavu pamäťového miesta, kde vznikla. Ale v prípade objektov, pri ktorých chceme dodržať nejakú vnútornú podmienku konzistencie, zrejme nebude vhodné ponechať jednotlivým údajovým členom náhodné hodnoty. Implicitný konštruktor preto obyčajne priraďuje členom triedy implicitné, vopred dohodnuté hodnoty (napríklad samé nuly).

Druhým zvláštnym typom konštruktora je kopírovací konštruktor (copy constructor), ktorý slúži na vytvorenie kópie existujúceho objektu. Preto ho musí byť možné zavolať s jediným argumentom – kopírovaným objektom. Keďže konštruktor triedy X nemôže mať argument typu X, kopírovací konštruktor musí byť deklarovaný s argumentom typu X&, resp. const X& (a prípadnými ďalšími implicitnými argumentmi).

Oba konštruktory, implicitný aj kopírovací, sú špecifické aj tým, že v prípade ich neprítomnosti si ich dokáže kompilátor vygenerovať sám. Implicitný konštruktor sa generuje len v prípade, že nie je definovaný žiaden iný konštruktor. V takom prípade treba minimálne umožniť deklarovať inštanciu triedy bez inicializácie. Takto vygenerovaný implicitný konštruktor je verejný (public) a dokopy nič nerobí, iba prípadne volá implicitné konštruktory základných tried a/alebo implicitné konštruktory vnorených objektov. Kopírovací konštruktor zase kompilátor doplní v prípade potreby vytvorenia kópie nejakého objektu, či už pri inicializácii iného objektu, pri použití operátora priradenia, pri odovzdávaní argumentov funkciám alebo pri spracovaní návratovej hodnoty. Takýto implicitne doplnený kopírovací konštruktor vytvorí už minule spomínanú plytkú kópiu objektu, t. j. skopíruje jednotlivé členy bajt po bajte do nového objektu. Treba podotknúť, že prekladačom generované konštruktory v programe existujú len vtedy, keď sú potrebné.

Aby sme sa však nepohybovali stále len v teoretickej rovine, ukážeme si teraz malý príklad. Deklarujeme triedu complex, ktorú budeme používať na reprezentáciu komplexných čísel. Trieda bude (zatiaľ) obsahovať dva údajové členy – reálnu zložku re a imaginárnu zložku im, obe typu double – a štyri konštruktory:

class complex
{
  double re, im;
 
public:
  complex()
  { re = im = 0.0; }
  complex(double r)
  { re = r; im = 0.0; }
  complex(double r, double i)
  { re = r; im = i; }
  complex(complex& c)
  { re = c.re; im = c.im; }
};

Trieda, samozrejme, nie je úplná, minimálne chýbajú funkcie na sprístupnenie oboch zložiek komplexného čísla. Tie si môžete doplniť sami. Ďalšie užitočné členské funkcie budeme dopĺňať priebežne počas výkladu.

Prvý konštruktor, complex::complex(), je spomínaným implicitným konštruktorom. Ako vidíme, nerobí nič iné, iba vynuluje obe zložky, reálnu aj imaginárnu. Takýto konštruktor sa uplatní napríklad pri nasledujúcich deklaráciách:

complex c1, c2;
complex* pc1 = new complex();

Oba objekty c1 a c2, ako aj objekt, na ktorý ukazuje ukazovateľ pc1, budú mať po vytvorení obe zložky nulové. Všimnite si použitie operátora new. Vieme už, že jeho operandom je typ, ktorého inštanciu chceme vytvoriť. Do zátvoriek za typ môžeme napísať inicializačnú hodnotu (napríklad new int(3) vytvorí novú premennú typu int a nastaví ju na hodnotu 3). No v prípade inštancií tried máme trochu širšie možnosti – do zátvoriek zapisujeme argumenty, pomocou ktorých chceme objekt skonštruovať. Na základe ich počtu a typu prekladač vyberie ten správny konštruktor, ktorý následne zavolá. Ak nijaký vhodný nenájde, ohlási chybu. Ak nechceme dodať žiadne argumenty, nemusíme pár zátvoriek vôbec písať (ako pri jednoduchých typoch):

complex* pc1 = new complex;

Druhý konštruktor, complex::complex(double, vhodne slúži na inicializáciu takých komplexných čísel, ktoré majú imaginárnu zložku nulovú. V rámci šetrenia miestom ich potom môžeme inicializovať takto:

complex c3(4);
complex* pc2 = new complex(3.14);

Objekt c3 bude mať po inicializácii reálnu zložku rovnú štyrom, imaginárnu konštruktor vynuluje. Podobne objekt *pc2. Vidíme, že argument konštruktora v prvom prípade uvedieme do zátvoriek za identifikátor deklarovanej premennej, v druhom prípade za názov vytváraného typu.

Tretí konštruktor, complex::complex(double, double), konečne umožňuje plnohodnotnú inicializáciu reálnej aj imaginárnej zložky celého objektu. Argumenty uvádzame podobne ako v predchádzajúcom prípade:

complex c4(9.2, 5.7);
complex* pc3 = new complex(4, 2.1);

Ak sa dobre zahľadíte na deklaráciu predchádzajúcich troch konštruktorov, zrejme si uvedomíte, že je ich možné spojiť do jedného, ktorý sa bude dokonca tváriť ako implicitný:

complex(double r = 0.0, double i = 0.0)
{ re = r; im = i; }

Možno ste si až teraz uvedomili, na čo sú dobré implicitné argumenty funkcií.

Posledným konštruktorom v triede complex je kopírovací konštruktor complex::complex(complex& c). V jeho tele, samozrejme, máme prístup k privátnym členom argumentu c, pretože sa nachádzame stále v tej istej triede. Použitie konštruktora je podobné predchádzajúcim príkladom:

complex c5(c3);
complex* pc4 = new complex(c2);

Ale navyše môžeme vytvárať kópie objektov triedy complex aj takýmto spôsobom:

complex c6 = c4;

Pravdu povediac, tento spôsob inicializácie je možný pri použití ľubovoľného konštruktora s jedným argumentom, ale celá problematika je trochu komplikovanejšia a budeme sa jej venovať o chvíľu. Podstatné je teraz, že kopírovací konštruktor sa volá ako dôsledok vytvorenia nového objektu. Ak by sme z predchádzajúceho riadku kódu vynechali špecifikátor complex, dostali by sme priradenie c6 = c4, pri ktorom sa uplatňuje priraďovací operátor =.

Dočasné objekty

Počas vykonávania jednotlivých príkazov programu je často výhodné, či dokonca nevyhnutné používať dočasné objekty. Takéto objekty nie sú nijako deklarované a existujú obyčajne ako vedľajší účinok vyhodnocovania niektorých výrazov, odovzdávania argumentov funkciám, ukladania návratovej hodnoty a pod. Môžeme ich však vytvoriť aj explicitne, uvedením názvu triedy spolu s argumentmi konštruktora v zátvorkách. Ukážme si príklad:

class C
{
public:
  C(int);
  C(C&);
  // ...
};
 
C f(C);
 
void g()
{
  C c1(5);
  C c2 = f(c1);
  C c3 = f(C(11));
  c1 = f(c1);
}

V príklade máme torzo triedy C, ktorá obsahuje jeden klasický konštruktor s jediným argumentom typu int a jeden kopírovací konštruktor. Funkcia f(), ktorej prototyp sa nachádza v príklade, má jediný argument typu C a vracia návratovú hodnotu rovnakého typu.

Prvý objekt c1 deklarujeme a inicializujeme známym spôsobom, po jeho vytvorení prekladač známym spôsobom zavolá konštruktor C::C(int) s argumentom 5. Druhý objekt c2 je inicializovaný návratovou hodnotou volania f(c1). Pri odovzdávaní argumentu c1 funkcii sa obyčajne (je to závislé od implementácie) vytvorí dočasný objekt typu C, ktorý bude kópiou c1. Tento dočasný objekt sa ako každá kópia inicializuje pomocou kopírovacieho konštruktora C::C(C&). Funkcii sa potom dodá známym spôsobom - skopírovaním na zásobník. Návratová hodnota funkcie, ktorá je v podstate takisto dočasným objektom, vytvoreným pri opúšťaní jej tela príkazom return s patričným argumentom, potom bude slúžiť ako vzor, na základe ktorého sa vytvorí objekt c2. Vzhľadom na tvar inicializácie c2 sa opäť použije kopírovací konštruktor. Je, samozrejme, možné, že na základe návratovej hodnoty sa vytvorí nový dočasný objekt a až ten bude slúžiť ako vzor na vytvorenie c2, presné detaily sú implementačne závislé. Ak chcete vedieť, aké rôzne dočasné objekty vznikajú, stačí doplniť do oboch konštruktorov nejaký ladiaci výpis – koľkokrát tento výpis uvidíte na obrazovke, toľkokrát sa konštruktor volal. Tento investigatívny spôsob výčby vám vrelo odporúčam, pretože len vďaka nemu získate presnú predstavu o tom, čo všetko program robí.

Tretí objekt c3 sa inicializuje podobným spôsobom ako c2, s tým rozdielom, že argumentom funkcie f() je nami explicitne vytvorený dočasný objekt triedy C. Pri jeho vytvorení sa použije vzhľadom na argument 11 konštruktor C::C(int). Takýto dočasný objekt môžeme vytvoriť hocikde, kde potrebujeme objekt triedy C, jeho životnosť je však obmedzená na výraz, v ktorom sa nachádza. Ak ho teda chceme použiť aj neskôr, budeme ho musieť buď priradiť inému objektu, alebo vytvoriť jeho kópiu (na báze kopírovacieho konštruktora). V každom prípade však dočasný objekt po použití zanikne.

V príklade máme ešte jeden riadok, na ktorom objektu c1 priraďujeme výsledok volania funkcie f() s týmto objektom ako argumentom. Je očividné, že pri vyhodnocovaní tohto výrazu dôjde ku vzniku jedného či viacerých dočasných objektov – typicky sa vytvorí pomocou kopírovacieho konštruktora kópia c1, tá sa odovzdá funkcii f(), ktorá vráti nejakú hodnotu typu C a táto hodnota sa pomocou priraďovacieho operátora (ktorý môže byť prekrytý! – o tom si ešte budeme hovoriť) priradí objektu c1.

Na tomto mieste je vhodné objasniť, akým spôsobom obyčajne vraciame z funkcie hodnotu typu triedy. Najprv predpokladajme, že návratovou hodnotou nejakej funkcie je „čistý“ (t. j. nemodifikovaný) typ triedy, napríklad complex. Návratovú hodnotu určíme ako argument príkazu return. Môže ním byť už existujúci globálny či lokálny objekt alebo môžeme použiť spomínaný explicitne vytvorený dočasný objekt. Druhý spôsob sa veľmi často používa v jednoduchších funkciách; ako príklad si doplníme do našej triedy complex členskú funkciu conj(), ktorá bude vracať číslo komplexne konjugované (pre tých, čo zabudli, k číslu a + bi je komplexne konjugované číslo abi). „Uvravenejšia“ verzia bude vyzerať asi takto:

complex complex::conj()
{
  complex c;
  c.re = re;
  c.im = -im;
  return c;
}

Načo však robiť veci zložito, keď to ide oveľa jednoduchšie:

complex complex::conj()
{
  complex c(re, -im);
  return c;
}

A úplne najjednoduchšie je to takto:

complex complex::conj()
{
  return complex(re, -im);
}

Oba posledné príklady sú si veľmi podobné a v podstate využívajú existenciu príslušného konštruktora. Opakovane upozorňujem, že presné detaily odovzdania návratovej hodnoty sú implementačne závislé, nedá sa teda dopredu povedať, koľko dočasných medziobjektov sa použije.

Objekt nejakej triedy však môžeme z funkcie vrátiť aj referenciou. V takom prípade si musíme dať pozor na to, aby sme nevracali automatickú premennú alebo dočasný objekt, pretože by sme dostali buď referenciu nikam, alebo tzv. dočasnú referenciu, čo je vlastne referencia na vytvorený dočasný objekt, ktorého životnosť bude rovnaká ako životnosť danej referencie. Pokiaľ naozaj potrebujeme vrátiť referenciou novo vytvorený objekt, pomôže nám operátor new. Nesmieme však zabudnúť po skončení práce objekt aj zrušiť pomocou delete. Malý príklad (bez výrazného zmyslu):

complex& complex::fnc()
{
  // ...
  return *(new complex(1, 2));
};

Konverzie

Nie je to tak dávno, čo sme sa zaoberali štandardnými konverziami medzi údajovými typmi C++. Pri tej príležitosti sme naznačili, že okrem nich C++ poskytuje možnosť tzv. používateľských konverzií, slúžiacich prakticky výhradne na prevod medzi objektovými a štandardnými typmi, resp. medzi objektovými typmi navzájom. Používateľské konverzie sú vyvolávané implicitne v podobných situáciách ako štandardné, teda. pri konverzii inicializátorov, argumentov a návratových hodnôt funkcií, pri konverzii operandov vo výrazoch, výrazov používaných v iteračných či vetviacich príkazoch a pod.

Používateľské konverzie možno definovať dvoma spôsobmi: pomocou konštruktorov a pomocou konverzných funkcií. Prvý spôsob zabezpečuje konverzie z iných typov na daný objektový typ, druhý spôsob konverzie presne opačné.

Konverzie pomocou konštruktorov

Tento spôsob konverzií je v podstate triviálny. Stačí do deklarácie triedy doplniť konštruktor, ktorý možno zavolať s jediným argumentom toho-ktorého typu. Príklad:

class C
{
public:
  C(int);
  C(char*, int = 0);
  // ...
};

S takto deklarovanou triedou môžeme používať nasledujúce príkazy:

C c1 = 1;

Objekt c1 je inicializovaný pomocou kopírovacieho konštruktora, ktorého argumentom je dočasný objekt C(1), vzniknutý konverziou čísla 1 na objekt triedy C pomocou konštruktora C::C(int). Riadok je v podstate ekvivalentný riadku:

C c1 = C(1);

Iná konverzia:

C c2 = "Niki";

V tomto prípade sa vyvolá konštruktor C::C(char*, int). Deklarácia je ekvivalentná tejto:

C c2 = C("Niki", 0);

Konverzie nie sú obmedzené len na deklarácie:

c1 = 123;

V tomto priradení sa číslo 123 pomocou konštruktora konvertuje na dočasný objekt C(123). Tento objekt sa potom pomocou priraďovacieho operátora skopíruje do c1.

Ak sa pri snahe o konverziu nenájde vhodný konštruktor, kompilátor neskúša, či sa náhodou nedá vykonať konverzia okľukou cez iný typ. Nasledujúci kód je preto chybný:

class A
{
public:
  A(int);
  // ...
};
 
class B
{
public:
  B(A);
  // ...
};
 
B b = 10;

V tomto prípade sa neskúša volanie B(A(10)), pokiaľ to sami prekladaču nenaznačíme zápisom:

B b = A(10);

Konverzné funkcie

Opačný smer konverzie majú na starosti konverzné funkcie. V skutočnosti ide o prekryté operátory pretypovania (o prekrývaní operátorov sme ešte nehovorili). Deklarujeme ich ako bežné členské funkcie, ale so špecifickým názvom, tvoreným kľúčovým slovom operator, za ktoré zapíšeme cieľový typ (štandardný či objektový, povolené sú aj kombinácie s operátormi * a &). Ako príklad si doplníme našu triedu complex o operátor konverzie na typ double. Výsledkom konverzie bude absolútna hodnota komplexného čísla:

#include <math.h>
 
class complex
{
  // ...
  operator double()
  { return sqrt(re * re + im * im); }
};

Všimnite si, že pri konverznej funkcii neuvádzame typ návratovej hodnoty – ten je už raz uvedený v názve funkcie. Vďaka doplnenej členskej funkcii môžeme bez problémov konvertovať typ complex na double a používať objekty triedy complex všade tam, kde sa očakáva typ double:

complex c;
double d = double(c);
d = (double)c;
d = c;
double e = c ? (c + 1) : c;
if (c) { ... };

Podobne ako pri konverzných konštruktoroch prekladač nebude hľadať možnosti konverzie okľukou cez iné typy:

class A
{
public:
  operator int();
  // ...
};
 
class B
{
public:
  operator A();
  // ...
};
 
B b;
int i = b;

V poslednom riadku je chyba, prekladač nebude skúšať konverziu b.operator A().operator int(). Môžeme mu to však naznačiť:

int i = A(b);

Povedali sme už, že prekladač v mnohých prípadoch vyvoláva konverzné funkcie implicitne. Môže sa však stať, že použijeme objekt nejakej triedy v takom kontexte, keď nebude jasné, ktorú z konverzných funkcií treba použiť. Vtedy sa nebude implicitne volať žiadna a prekladač ohlási chybu. Typický príklad:

class C
{
public:
  operator int();
  operator void*();
  // ...
};
 
C c;
if (c)
{ ... }

Z kontextu nie je jasné, či sa má objekt c v rámci podmienkového výrazu príkazu if konvertovať na typ int alebo na typ void*. Jediným riešením je v tomto prípade explicitné pretypovanie.

Inicializácia objektov

Zopakujme si, čo už vieme o možnostiach inicializácie objektov. V prípade, že trieda má iba verejné nestatické členy, nemá nijaký konštruktor, neobsahuje virtuálne funkcie a nie je odvodená od iných tried, môžeme jej inštancie inicializovať podobne ako štruktúry zoznamom inicializačných hodnôt jednotlivých členov. Trieda, ktorá má konštruktor, musí byť buď inicializovaná explicitne, alebo musí mať implicitný konštruktor, ktorý sa použije v prípade, že objekt nie je inicializovaný explicitne.

V prípade, že inicializujeme objekt pomocou jeho konštruktora, môžeme do zátvoriek uviesť zoznam argumentov tohto konštruktora. Alternatívny spôsob je uvedenie jedinej hodnoty za operátor =. Táto hodnota sa (po prípadných konverziách) uvedie ako argument kopírovacieho konštruktora.

Majme vyššie deklarovanú triedu complex so štyrmi konštruktormi. Uvedieme si pre zhrnutie rôzne možnosti inicializácie:

complex c1(2);

Objekt c1 sa inicializuje pomocou konštruktora complex::complex(double).

complex c2 = c1;

Objekt c2 sa inicializuje pomocou kopírovacieho konštruktora kópiou objektu c1.

complex c3 = complex(9, 11);

Pomocou konštruktora complex::complex(double, double) sa skonštruuje dočasný objekt complex(9, 11), ktorým sa na základe kopírovacieho konštruktora inicializuje objekt c3.

complex c4;

Objekt c4 sa inicializuje implicitným konštruktorom complex::complex().

complex c5 = 42;

Prostredníctvom konštruktora complex::complex(double) sa skonštruuje dočasný objekt complex(42). Ním sa pomocou kopírovacieho konštruktora inicializuje objekt c5.

Dôležité je, že všetky uvedené príklady sú deklarácie, preto ani uvedenie operátora = v nich neznamená priradenie, ale inicializáciu. To je dôležité vedieť vtedy, keď potrebujeme rozlišovať medzi kopírovacím konštruktorom a prekrytým priraďovacím operátorom.

Inicializácia, ktorá nastáva ako dôsledok odovzdávania argumentov a návratových hodnôt funkcií, je ekvivalentná tvaru:

T x = a;

kde T je príslušný objektový typ, x je formálny argument/návratová hodnota a a je skutočný argument/skutočne vracaná hodnota. Inicializácia spojená s alokáciou dynamického objektu operátorom new je ekvivalentná tvaru:

T x(a);

Inicializovať môžeme aj polia objektov. Ak neuvedieme zoznam inicializačných hodnôt, použije sa implicitný konštruktor, ktorý musí v triede existovať, inak prekladač ohlási chybu. Ak zoznam uvedieme, musí v triede existovať konštruktor, ktorý prijíma jediný argument. Prekladač pri inicializácii postupne berie prvok za prvkom z inicializačného zoznamu a na ich základe konštruuje jednotlivé objekty poľa. Pokiaľ potrebujeme skonštruovať objekty na základe viacargumentových konštruktorov, použijeme takúto fintu:

complex cc[4] = {
  3,
  complex(),
  5,
  complex(7.6, 1.1)
};

Prvky cc[0] a cc[2] sa inicializujú pomocou complex::complex(double), prvok cc[1] pomocou complex::complex() a konečne prvok cc[3] pomocou complex::complex(double, double).

V prípade, že inicializačný zoznam má menej prvkov, ako je deklarovaný rozmer poľa objektov, musí mať trieda implicitný konštruktor, ktorý sa použije na skonštruovanie zostávajúcich prvkov.

Inicializovať uvedeným spôsobom nemôžeme pole objektov vytvorené dynamicky, pomocou operátora new. V takom prípade musia mať objekty implicitný konštruktor, ktorý sa vyvolá pre každý prvok novo vytvoreného poľa.

Deštruktor

Tak ako máme možnosť objekt po jeho vytvorení inicializovať pomocou konštruktora, C++ nám poskytuje možnosť objekt pred jeho zrušením aj správne „zlikvidovať“. Takáto likvidácia obyčajne spočíva v uvoľnení alokovanej pamäte, prostriedkov a prípadne v iných upratovacích činnostiach. Zjednodušene povedané, čo konštruktor vytvoril, to deštruktor zruší. K volaniu deštruktora dochádza vždy vtedy, keď  príslušný objekt končí svoju životnú púť: automatické objekty pri opustení príslušného bloku, statické po skončení programu a dynamicky alokované zavolaním operátora delete.

Deklarácia deštruktora je v princípe podobná deklarácii konštruktora. Meno deštruktora dostaneme pripojením znaku ~ (tilda) pred meno triedy. Deštruktor nesmie mať nijaké argumenty a podobne ako konštruktor nemá deklarovaný typ návratovej hodnoty (ani ako void). Takisto nemôže byť deštruktor deklarovaný ako const alebo volatile a nesmie byť ani statický. Na rozdiel od konštruktora však môže mať trieda virtuálny deštruktor. Z tela deštruktora možno volať ostatné členské funkcie. Prvky poľa sa deštruujú v opačnom poradí, ako boli konštruované.

Ako príklad na deštruktor si uvedieme triedu string, ktorá bude zapuzdrovať reťazec znakov. Jediným jej údajovým členom bude ukazovateľ na obsiahnutý reťazec. Do triedy zavedieme aj niekoľko konštruktorov:

class string
{
  char* str;
 
public:
  string(const char* s)
  {
    str = new char[strlen(s) + 1];
    strcpy(str, s);
  }
 
  string(const string& s)
  {
    str = new char[strlen(s.str) + 1];
    strcpy(str, s.str);
  }
 
  ~string()
  {
    delete str;
  }
};

Trieda, samozrejme, pre jednoduchosť netestuje, či nie sú argumenty konštruktorov náhodou nulové ani či sa alokácia podarila. Vidíme, že jedinou starosťou deštruktora string::~string() je delokácia miesta, na ktoré ukazuje ukazovateľ str.

Možno si spomeniete, ako sme kedysi dávno, pri výklade o dynamickej alokácii a dealokácii pamäte, hovorili o tom, že pre zrušenie alokovaného poľa objektov musíme použiť mierne odlišnú syntax operátora delete:

string* as = new string[10];
// ...
delete [] as;

(Pozor, tento príklad nie je celkom správny, trieda string, ako sme ju zatiaľ definovali, neobsahuje implicitný konštruktor!) Za operátor delete musíme v tomto prípade pripísať ešte dvojicu zátvoriek [], aby sme zabezpečili, že sa pre každý prvok poľa as pri rušení tohto poľa vyvolá jeho deštruktor.

Nabudúce

Zostáva nám objasniť si ešte niekoľko maličkostí týkajúcich sa konštruktorov a deštruktorov, najprv však musíme prebrať dedenie medzi triedami a prekrývanie operátorov. V budúcej časti, resp. častiach sa budeme zaoberať práve vzťahmi dedičnosti, virtuálnymi funkciami a virtuálnymi základnými triedami.

C++

Nechajte si posielať prehľad najdôležitejších správ emailom

Mohlo by Vás zaujímať

Ako na to

Tipy a triky: Ako na snímku obrazovky na akomkoľvek počítači s Windows?

02.12.2016 00:13

Ak snímky obrazovky robíte často apotrebujete napríklad funkcie na posun stránok alebo snímanie zobrazenia pri vyššom rozlíšení displeja, zrejme používate nejakú špecializovanú aplikáciu. Väčšina použ ...

Ako na to 1

Tipy a triky: Ako aplikácii prednastaviť spúšťanie s administrátorskými právami?

30.11.2016 00:10

Väčšina aspoň trochu skúsenejších používateľov vie, že aj keď máte na operačnom systéme Windows vytvorený administrátorský účet, aplikácie pre bezpečnosť nefungujú vždy splnými administrátorskými práv ...

Ako na to 2

Tipy a triky: Ako vypnúť uzamykaciu obrazovku vo Windows 10?

29.11.2016 00:10

Rozčuľuje vás, že pred každým prihlásením doúčtu vášho počítača musíte prejsť uzamykacou obrazovkou? Windows 10 na tejto obrazovke ukazuje čas,dátum anejakú zaujímavú fotografiu zrôznych kútov sveta. ...

Žiadne komentáre

Vyhľadávanie

Kyocera - prve-zariadenia-formatu-a4-s-vykonom-a3

Najnovšie videá