Kurz C++ (14.)

V dneÜnφm pokraΦovßnφ se jeÜt∞ vrßtφme k p°et∞₧ovßnφ operßtor∙ a to konkrΘtn∞ k operßtor∙m new a delete. Potom se vrßtφme k tomu, co jsme naΦali minule, tedy d∞diΦnosti. Povφme si n∞co o jednoduchΘ d∞diΦnosti a vÜe si ukß₧eme na p°φkladech.

14.1. Operßtory new a delete

    Jak ji₧ vφme operßtory new a delete slou₧φ k alokaci a uvoln∞nφ dynamicky alokovanΘ pam∞ti. Oba operßtory existujφ ve dvou verzφch a to operßtor pro jednoduchΘ prom∞nnΘ:

void* operator new(size_t _velikost);

a pak je tu operßtor, kter² je urΦen pro alokace polφ:

void* operator new[](size_t _velikost);

    Pou₧itφm operßtoru new dojde prßv∞ na zßklad∞ druhu prom∞nnΘ, pro kterou chceme alokovat pam∞¥ k zavolßnφ jednΘ z t∞chto funkcφ. Operßtor delete mß takΘ dv∞ verze:

void operator delete(void *ptr);

, resp. pro pole:

void operator delete[](void *p);

    Je vhodnΘ poznamenat, ₧e operßtor new se starß pouze o alokaci pam∞ti, tedy nap°. volßnφ konstruktoru je starostφ p°ekladaΦe. Stejn∞ tak i volßnφ destruktoru v p°φpad∞ operßtoru delete. Jak ji₧ vφme, operßtor new v p°φpad∞, ₧e nenφ dostatek pam∞ti pro uspokojenφ po₧adavku, vracφ NULL. Jinou reakcφ na nedostatek pam∞ti m∙₧e b²t vyvolßnφ v²jimky, ale t∞mi se budeme zab²vat a₧ v n∞kterΘm z nßsledujφcφch dφl∙.

    Narozdφl od ostatnφch operßtor∙ lze tyto dva operßtory krom∞ p°etφ₧enφ i p°edefinovat, Φφm₧ dojde k nahrazenφ standardnφch verzφ po celou dobu b∞hu programu. Jednφm z d∙vod∙ pro p°edefinovßnφ t∞chto operßtor∙ m∙₧e b²t pot°eba ov∞°enφ, zdali sprßvn∞ uvol≥ujeme pam∞¥ nebo inicializace bloku pam∞ti p°edem zadanou hodnotou. Mohli bychom takΘ v operßtorovΘ funkci new zv∞tÜit o mal² kousek alokovan² ·sek, kter² bychom vyplnili p°edem zadanou hodnotou a p°i uvol≥ovßnφ bychom tento ·sek mohli zkontrolovat. Pokud by byl zm∞n∞n, pak pravd∞podobn∞ doÜlo k necht∞nΘmu p°φstupu do pam∞ti, kterß nßm u₧ nepat°φ. V ukßzce bude program p°i provedenφ alokace vypisovat kolik byte pam∞ti si u₧ivatel p°eje p°id∞lit a p°i uvoln∞nφ pak vypφÜe adresu bloku pam∞ti, kter² se uvol≥uje:

#include "stdafx.h"    // Pouzivame predkompilovane hlavicky

#include <stdio.h>
#include <malloc.h>
#include <iostream.h>

void* operator new(size_t _velikost)
{
    void* p = NULL;

    printf("Pokus o alokaci %u byte ... ", _velikost);

    if(!_velikost) { _velikost = 1; } // operator new by mel vracet ruzne adresy

    p = malloc(_velikost);
    if(p)
    {
        printf("uspesne (adresa : %p)\n", p);
    }
    else
    {
        printf("neuspesne\n");
    }

    return p;
}

void operator delete(void* _ptr)
{
    printf("Dealokace bloku na adrese %p\n", _ptr);

    if(_ptr)
    {
        free(_ptr);
    }
}

int main(int argc, char* argv[])
{
    int i = 0;
    int *p_int;

    p_int = new int;

    *p_int = i;

    int *p_int2;
    p_int2 = new int[4096];

    delete p_int2;
    delete p_int;

    char c;
    cin >> c;

    return 0;
}

    Zdrojov² k≤d naleznete v sekci Downloads (projekt PretNew).

    U standardnφho operßtoru new je v dokumentaci uvedeno, ₧e v p°φpad∞ opakovanΘ alokace 0 byte operßtor new vracφ ukazatele na r∙znΘ oblasti pam∞ti. To mßme nynφ zajiÜt∞no alokacφ 1 byte. Tedy i kdy₧ u₧ivatel chce (spφÜe ale nechce) blok pam∞ti, vrßtφme mu jeden byte.

    Pokud se rozhodneme, ₧e se nßm funkce printf() nelφbφ a nahradφme ji volßnφm cout dostaneme se do potφ₧φ. Tento objekt toti₧ intern∞ pou₧φvß operßtory new  a delete k alokovßnφ a uvoln∞nφ bloku pomocnΘ pam∞ti. AvÜak nßmi definovan² operßtor nahradil standardnφ verzi, Φφm₧ dojde k rekurzivnφmu volßnφ funkce, kterΘ ale chybφ ukonΦovacφ podmφnka. Tφm m∙₧e u n∞kter²ch implementacφ vzniknout nekoneΦn² cyklus.

    Kv∙li v²Üe uvedenΘmu se tedy redefinici operßtor∙ new a delete na globßlnφ ·rovni sna₧φme vyhnout. Pou₧ijeme tedy mo₧nosti p°etφ₧enφ operßtoru new jako metody n∞jakΘ t°φdy nebo jako globßlnφho operßtoru s p°idan²mi parametry, kdy se m∙₧eme rozhodnout jestli pou₧ijeme naÜφ verzi nebo standardnφ:

void* operator new(size_t _velikost, int _iMuj)
{
    printf("Toto je muj operator new");

    return operator new(_velikost);
}


int main(int argc, char* argv[])
{
    int *p_int;
    p_int = new(5) int;    // Dojde k zavolani naseho operatoru new

    delete p_int;
    return 0;
}

    Zdrojov² k≤d naleznete v sekci Downloads (projekt PretNew2).

    Podobn∞ jako jsme p°et∞₧ovali operßtory new a delete bychom mohli p°etφ₧it jejich verze pro pole, operßtory new[] a delete[]. Ve v∞tÜin∞ p°φpad∙ je to vÜak zbyteΦnΘ, nebo¥ tyto operßtory intern∞ pou₧φvajφ operßtor∙ new a delete.

    Nynφ si ukß₧eme jak p°etφ₧it operßtor pro t°φdu:

#include "stdafx.h"    // Pouzivame predkompilovane hlavicky

#include <stdlib.h>
#include <memory.h>
#include <iostream.h>

class Test {
public:
    void *operator new(size_t _velikost, char _cznak);
    char VratZnak() { return m_cZnak; }
private:
    char m_cZnak;
};

void* Test::operator new(size_t _velikost, char _cZnak)
{
    void *p = malloc(_velikost);
    if(p)
    {
        memset(p, _cZnak, _velikost);
    }
    return p;
}

int main(int argc, char* argv[])
{
    Test *cTest = new('G') Test;

    cout << "Znak je : " << cTest->VratZnak() << endl;

    char c;
    cin >> c;

    return 0;
}

    P°i alokaci pam∞ti cel² ·sek nastavφme na zadanou hodnotu. Globßlnφ operßtor new je p°ed touto t°φdou skryt, tak₧e p°φkaz:

    Test *cTest = new Test;

    vyvolß chybu p°i p°ekladu. Pokud bychom ovÜem pot°ebovali pro alokaci pou₧φt standardnφho operßtoru new, pom∙₧e nßm nßsledujφcφ °ßdek:

    Test *cTest = ::new Test;

    P°etφ₧φme-li operßtory new a delete jako metody objektovΘho typu, jsou v₧dy statickΘ. A to i v p°φpad∞, ₧e klφΦovΘ slovo static neuvedeme.

    Zdrojov² k≤d naleznete v sekci Downloads (projekt PretNew3).

    JeÜt∞ se podφvßme na zvlßÜtnφ operßtor new, kter² je v souboru new.h. Je definovßn takto:

void* operator new(size_t _velikost, void* p)
{
    return p;
}

Jak vidφme, tak tento operßtor ve svΘm t∞le neobsahuje ₧ßdnΘ p°φkazy pro alokaci bloku pam∞ti. K Φemu se tedy vlastn∞ hodφ? Podφvejme se na nßsledujφcφ p°φklad:

#include "stdafx.h"    // Pouzivame predkompilovane hlavicky

#include <iostream.h>

void* operator new(size_t _velikost, void *_p)
{
    return _p;
}

void operator delete(void *_ptr, void *_p2)
{
    ;
}

class Test
{
private:
    char a, b;
public:
    Test() { a = 'A'; b = 'B'; }
};

char pole[64];


int main(int argc, char* argv[])
{
    // Zinicializujeme na X
    for(int i = 0; i < 64; i++)
    {
        pole[i] = 'X';
    }

    Test *mujtest = new(pole) Test;

    cout << "Adresa pole je : " << &pole << endl;
    cout << "Adresa objektu je : " << mujtest << endl;

    for(i = 0; i < 64; i++)
    {
        cout << pole[i];
    }

    char c;
    cin >> c;

    return 0;
}

    Zdrojov² k≤d naleznete v sekci Downloads (projekt PretNew4).

    Na v²stupu programu pak uvidφme, ₧e se adresy pro pole znak∙ a instanci t°φdy Test shodujφ. Takto definovan² operßtor tedy jen vrßtφ ukazatel na zadanou adresu. Pokud si pak vypφÜeme obsah pole, uvidφme, ₧e pole ve sv²ch dvou prvnφch bytech obsahuje ΦlenskΘ prom∞nnΘ t°φdy Test. Prßzdn² operßtor delete je uveden, aby p°ekladaΦ nehlßsil upozorn∞nφ, ₧e neexistuje odpovφdajφcφ operßtor delete, kter² by uvolnil pam∞¥ pokud by vznikla p°i inicializaci v²jimka. Pokud chceme definovat odpovφdajφcφ operßtor delete k operßtoru new, pak musφ mφt parametry shodnΘ od druhΘho a p°φpadn∞ v²Üe. Tento operßtor nemusφme vlastnoruΦn∞ p°et∞₧ovat, staΦφ pouze vlo₧it soubor new.h do naÜeho programu.

    Jin²m p°φkladem by mohlo b²t p°etφ₧enφ operßtoru new jako metody t°φdy. V programu bychom pak definovali ·sek pam∞ti (pole) pro vytvß°enφ instancφ tΘto t°φdy. Toto pole by mohlo b²t takΘ statickou Φlenskou prom∞nnou danΘ t°φdy. Operßtor new by pak vracel ukazatele do tohoto pole a v ΦlenskΘ statickΘ prom∞nnΘ by si udr₧oval index na zb²vajφcφ volnΘ mφsto v tomto poli. Pokud bychom v tomto poli cht∞li uklßdat i pole objekt∙, tedy pomocφ operßtoru new[], museli bychom ho v tΘto t°φd∞ p°etφ₧it. Jinak by se pou₧il globßlnφ operßtor, kter² by alokoval blok pam∞ti z dostupnΘ volnΘ pam∞ti. Pro alokovßnφ pam∞ti pro t°φdu Test mimo pole (tedy ve volnΘ pam∞ti) lze op∞t pou₧φt globßlnφ operßtor new, kter² musφme kvalifikovat Φty°teΦkou (::).

    A koneΦn∞, chceme-li jen alokovat pam∞¥ (bez zavolßnφ konstruktoru) nebo pam∞¥ uvolnit (bez volßnφ destruktoru):

Test *bezvolani = (Test *)operator new(sizeof(Test));
// Pouziti
operator delete(bezvolani);

    Zdrojov² k≤d naleznete v sekci Downloads (projekt PretNew5).

14.2. D∞diΦnost - pokraΦovßnφ

    V minulΘm dφle jsme se dov∞d∞li n∞jakΘ obecnΘ informace o d∞diΦnosti. Dnes si ukß₧eme jak pou₧φt jednoduchΘ d∞diΦnosti. ZaΦneme ale pojmem hierarchie t°φd. Slo₧it∞jÜφ programy se neobejdou pouze s jednou t°φdou, mohou jich mφt stovky. P°i pou₧itφ d∞diΦnosti vzniknou p°φbuzenskΘ vztahy mezi n∞kter²mi t°φdami. Tuto strukturu naz²vßme hierarchiφ t°φd. Pro minule uveden² p°φklad se zoologickou zahradou by mohl vypadat nap°φklad takto:

    Vidφme, ₧e hierarchie pro jednoduchou d∞diΦnost mß tvar stromu. Libovolnß t°φda, krom∞ zßkladnφ, mß pouze jednoho p°edka. T°φdu na nejvyÜÜφ ·rovni naz²vßme ko°enovou t°φdu (angl. root), je to spoleΦn² prvek pro vÜechny t°φdy v hierarchii. T°φda ze kterΘ vede Φßra je t°φdou odvozenou, t°φda v nφ₧ konΦφ Üipka je t°φdou zßkladnφ. Sm∞r Üipky je zvolen tφmto zp∙sobem, proto₧e odvozenß t°φda znß svou zßkladnφ t°φdu, kde₧to zßkladnφ t°φda nevφ, kterΘ dalÜφ t°φdy z nφ budou odvozeny. Jazyk C++ nßm krom∞ jednoduchΘ d∞diΦnosti umo₧≥uje pou₧φt d∞diΦnost vφcenßsobnou a d∞diΦnost opakovanou. P°i vφcenßsobnΘ d∞diΦnosti mß t°φda vφce rodiΦ∙, v grafu tedy bude ukazovat na vφce t°φd v hierarchii. P°i opakovanΘ d∞diΦnosti, ke kterΘ dochßzφ p°edevÜφm ve slo₧it∞jÜφch hierarchiφch, zd∞dφ t°φda vlastnosti n∞kterΘho p°edka vφce cestami.

14.2.1. Jednoduchß d∞diΦnost

    M∞jme t°φdu nebo strukturu Predek. Potom t°φdu nebo strukturu Potomek, odvozenou od t°φdy Predek, deklarujeme nßsledovn∞:

class Potomek : Predek {
/*Pridane prvky*/
};

nebo

struct Potomek : Predek {
/*Pridane prvky*/
};

    V C++ je struktura vlastn∞ to samΘ co t°φda, s tφm rozdφlem, ₧e vÜechny jejφ prvky jsou implicitn∞ ve°ejnΘ (public). V obou p°φpadech m∙₧eme p°ed jmΘnem t°φdy, od kterΘ d∞dφme uvΘst specifikßtory p°φstupu (public, private, protected). Pokud neuvedeme ₧ßdn² z t∞chto specifikßtor∙ platφ nßsledujφcφ: pokud je d∞d∞n² objekt t°φdou platφ implicitn∞ private, u struktur public. Nßsledujφcφ tabulka ukazuje, jak budou ovlivn∞na p°φstupovß prßva ve zd∞d∞nΘ t°φd∞:

p°φstupovΘ prßvo v rodiΦovskΘm objektu

public

protected

private

odvozenφ public

public

protected

nep°φstupn²

odvozenφ protected

protected

protected

nep°φstupn²

odvozenφ private

private

private

nep°φstupn²

    Äe je prvek nep°φstupn² znamenß, ₧e k n∞mu nenφ mo₧no p°istupovat v t∞le metod p°idan²ch do odvozenΘ t°φdy. K p°φstupu k nim je tedy nutnΘ pou₧φt Φlensk²ch metod, jako bychom k nim p°istupovali z vn∞jÜku t°φdy. Chrßn∞nΘ prvky m∙₧eme pou₧φt v nov∞ p°idan²ch metodßch odvozenΘ t°φdy, ale nejsou p°φstupnΘ z venku. Ve°ejnΘ prvky lze op∞t vyu₧φt v nov∞ p°idan²ch metodßch odvozenΘ t°φdy, ale krom∞ toho jsou jeÜt∞ p°φstupnΘ vn∞jÜφm p°φstupem.

    Vφme-li, ₧e od danΘ t°φdy budeme chtφt d∞dit a v p°idan²ch metodßch pak p°istupovat k Φlensk²m prom∞nn²m zßkladnφ t°φdy, je nejlepÜφ pou₧φt pro prom∞nnΘ v zßkladnφ t°φd∞ p°φstupovΘho prßva protected a t°φdu zd∞dit pomocφ specifikßtoru p°φstupu public. Vyhneme se tφm zbyteΦnΘmu volßnφ metod zp°φstup≥ujφcφch danΘ prom∞nnΘ a zachovßme pravidlo, ₧e prom∞nnΘ nebudou p°φstupnΘ zvn∞jÜku objektu. V p°φkladech v∞novan²ch d∞diΦnosti budeme v∞tÜinou pou₧φvat prßv∞ tohoto zp∙sobu.

    Pokud chceme, m∙₧eme u vybran²ch prom∞nn²ch vrßtit jejich p°φstupovß prßva na ·rove≥ kterou m∞la ve t°φd∞ od nφ₧ odvozujeme. Ukßzka:

class Predek {
public:
    char a;
};

class Potomek : Predek {
public:
    Predek::a;    // Pokud neni uvedeno, prekladac pri primem pristupu k prvku a ohlasi chybu (public se totiz pri
                     private dedeni stane private)

};

    To samΘ lze za°φdit pomocφ nov∞jÜφho zßpisu:

class Potomek : Predek {
public:
    using Predek::a;
};

    Pro zm∞n∞nφ p°φstupovΘho prßva metody je nutnΘ uvΘst v obou p°φpadech metodu bez zßvorek.

    Pokud se nßm nezdß vhodnΘ ponechat p°φstup k n∞jakΘ metod∞ rodiΦovskΘ t°φdy, lze ji zakßzat. Provedeme to tak, ₧e v odvozenΘ t°φd∞ deklarujeme soukromou metodu se stejn²m typem a parametry. Tato metoda p∙vodnφ metodu zastφnφ a tφm znemo₧nφ jejφ pou₧itφ vn∞ t°φdy. V p°φpad∞, ₧e bychom ji cht∞li zavolat uvnit° n∞jakΘ ΦlenskΘ funkce musφme ji zavolat jejφm cel²m jmΘnem, tedy nap°. Predek::Metoda().

    Existujφ takΘ prvky, kterΘ se ned∞dφ. Ned∞dφ se konstruktor. Jeho volßnφ za°φdφ p°ekladaΦ, pop°φpad∞ ho lze v konstruktoru odvozenΘ t°φdy zavolat explicitn∞. Podobn∞ se ned∞dφ destruktor, ale je op∞t vyvolßn p°ekladaΦem. A takΘ se ned∞dφ p°etφ₧en² operßtor operator=(). Pokud nenφ explicitn∞ definovßn pro odvozenou t°φdu, vyu₧ije se operßtor p°edka p°i konstrukci implicitnφho p°i°azovacφho operßtoru potomka.

14.3. Co bude p°φÜt∞

    P°φÜt∞ se podφvßme podrobn∞ji na volßnφ konstruktor∙ a destruktor∙ v odvozen²ch t°φdßch. Dßle virtußlnφmu d∞d∞nφ, virtußlnφm metodßm a abstraktnφm t°φdßm.

PřφÜtě nashledanou.

Ond°ej BuriÜin