IT rekvalifikace s garancí práce. Seniorní programátoři vydělávají až 160 000 Kč/měsíc a rekvalifikace je prvním krokem. Zjisti, jak na to!
Hledáme nové posily do ITnetwork týmu. Podívej se na volné pozice a přidej se do nejagilnější firmy na trhu - Více informací.

Lekce 4 - Destruktory a aplikace konstruktorů v C++

V minulé lekci, Hrací kostka v C++ a konstruktory, jsme si popsali syntaxi konstruktorů a to včetně pokročilých konstrukcí jako byl delegující konstruktor a volání konstruktorů pro atributy.

Dnes si popíšeme destruktory a ukážeme si hlavní účel, pro který jsou konstruktory a destruktory použity.

Destruktory

Podobně jako konstruktor, který se volá ihned po vytvoření instance, se destruktor volá automaticky před smazáním instance. Mazání obecně probíhá na konci bloku (tedy konec funkce nebo při uzavírající složené závorce }). Destruktor se zapisuje jako metoda, která začíná vlnovkou (~), po které následuje název třídy. Destruktor nikdy nemá parametry a nevrací hodnotu. Takový základní destruktor nám již Visual Studio vygenerovalo a je prázdný (pokud destruktor nedodáme, kompilátor automaticky vytvoří destruktor s prázdným tělem):

Kostka.h

class Kostka
{
public:
    Kostka();
    Kostka(int _pocet_sten);
    ~Kostka();  // deklarace destruktoru
    int pocet_sten;
};

Kostka.cpp

Kostka::~Kostka()  // prázdný destruktor
{
}

Abychom viděli, kdy se destruktor volá, přidáme si do jeho implementace výpis do konzole:

#include <iostream>  // pokud v souboru chybí
using namespace std;  // pokud v souboru chybí
Kostka::~Kostka()
{
    cout << "Volani destruktoru pro kostku s " << pocet_sten << " stenami" << endl;
}

Do main.cpp si vložíme následující kód, který ukazuje případy, kdy se destruktor volá.

void funkce(Kostka k)
{
    cout << "Funkce" << endl;
}
int main()
{
    Kostka prvni(1);
    if (true)
    {
        Kostka druha(2);
        funkce(druha);
        cout << "Funkce skoncena" << endl;
    }
    // cin.get();
    return 0;
}
#include <iostream>
#ifndef __KOSTKA_H__
#define __KOSTKA_H__
using namespace std;
class Kostka
{
public:
    Kostka();
    Kostka(int _pocet_sten);
    ~Kostka();
    int pocet_sten;
};
#endif
#include <iostream>
#include "Kostka.h"
using namespace std;
Kostka::Kostka() : Kostka(6)
{
    cout << "Volani bezparametrickeho konstruktoru" << endl;
}

Kostka::Kostka(int _pocet_sten)
{
    cout << "Volani konstruktoru s parametrem" << endl;
    pocet_sten = _pocet_sten;
}

Kostka::~Kostka()
{
    cout << "Volani destruktoru pro kostku s " << pocet_sten << " stenami" << endl;
}

Výpisy vidíme na výstupu aplikace:

Konzolová aplikace
Volani konstruktoru s parametrem
Volani konstruktoru s parametrem
Funkce
Volani destruktoru pro kostku s 2 stenami
Funkce skoncena
Volani destruktoru pro kostku s 2 stenami
Volani destruktoru pro kostku s 1 stenami

Když si příklad rozebereme, zjistíme, že destruktor se volá před ukončovacími složenými závorkami a na konci funkce. Tehdy již není proměnná potřeba a C++ provede její odstranění z paměti. Pro lepší pochopení přikládám kód s komentáři.

void funkce(Kostka k)
{
    cout << "Funkce" << endl;
} // destruktor pro "k"

int main()
{
    Kostka prvni(1); // první konstruktor
    if (true)
    {
        Kostka druha(2); // druhý konstruktor
        funkce(druha);
        cout << "Funkce skoncena" << endl;
    } // destruktor pro "druha"
    // cin.get(); pokud volání necháme, neuvidíme mazání "prvni"
    return 0;
} // destruktor pro "prvni"

Konstruktory jsou vypsané také, protože jsme ponechali kód z minulé lekce. Mohlo by vás zarazit, že jsou volány tři destruktory, ale pouze dva konstruktory. V jednom případě se volá kopírující konstruktor, ale tím se budeme zabývat v jiné lekci. Prozatím nám stačí vědět, kdy je destruktor volán.

Konstruktor pro inicializaci

Nyní se podíváme na jeden případ, kdy se nám konstruktory hodí - inicializace třídy.

Definujme na kostce metodu hod(), která nám vrátí náhodné číslo od 1 do počtu stěn. Je to velmi jednoduché, metoda nebude mít žádný parametr a návratová hodnota bude typu int. Náhodné číslo získáme tak, že zavoláme funkci rand() z knihovny cstdlib.

Kostka.h

#ifndef __KOSTKA_H__
#define __KOSTKA_H__

class Kostka
{
public:
    Kostka();
    Kostka(int _pocet_sten);
    ~Kostka();
    int hod();
    int pocet_sten;
};
#endif

Kostka.cpp

#include <iostream>
#include <cstdlib>
#include "Kostka.h"
using namespace std;
// ... již definované metody
int Kostka::hod()
{
    return rand() % pocet_sten + 1;
}

rand() vrací pseudo náhodné číslo. Aby bylo v požadovaném rozsahu, musíme na něj použít %pocet_sten. Jednička se přičítá proto, aby náhodná čísla byla od jedničky a ne od nuly. Pseudonáhodné číslo znamená, že se začne na nějakém čísle a nějakou operací se od něj dopočítávají zbývající čísla. To má jednu nevýhodu - do main.cpp napište následující kód (ten původní můžete smazat):

#include <iostream>
#include "Kostka.h"
#include "Arena.h"

using namespace std;


int main()
{
    Kostka kostka;
    for (int i = 0; i < 10; i++)
        cout << kostka.hod() << " ";
    cin.get();
    return 0;
}

Všimněte si, že pokud program spustíme opakovaně, vždy generuje stejné čísla (i když by je měl generovat náhodně). To je z důvodu, že výchozí číslo pro generování je pokaždé stejné. My potřebujeme, aby se při každém spuštění začínalo od jiného čísla, toho docílíme pomocí metody srand(), do které předáme aktuální čas. A protože to vlastně nastavuje instanci, tento kód vložíme do konstruktoru.

Pozn.: Vedle knihovny cstdlib musíme includovat i knihovnu ctime.

//main.cpp
int main()
{
    Kostka kostka;
    for (int i = 0; i < 10; i++)
        cout << kostka.hod() << " ";
    cin.get();
    return 0;
}
#ifndef __KOSTKA_H__
#define __KOSTKA_H__

class Kostka
{
public:
    Kostka();
    Kostka(int _pocet_sten);
    ~Kostka();
    int pocet_sten;
    int hod();
};
#endif
#include "Kostka.h"
#include <iostream>
#include <cstdlib>
#include <ctime>
using namespace std;

Kostka::Kostka() : Kostka(6)
{
    cout << "Volani bezparametrickeho konstruktoru" << endl;
}

Kostka::Kostka(int _pocet_sten)
{
    cout << "Volani konstruktoru s parametrem" << endl;
    pocet_sten = _pocet_sten;
    srand(time(NULL));
}
// zbývající metody

Kostka::~Kostka()
{
    cout << "Volani destruktoru pro kostku s " << pocet_sten << " stenami" << endl;
}

int Kostka::hod()
{
    return rand() % pocet_sten + 1;
}

Nyní kostka vždy vygeneruje jiná čísla a tím jsme hotovi.

Konstruktor pro správu paměti

Druhým případem, kdy můžeme použít konstruktor (a destruktor), je pro správu paměti. Díky tomu, že se konstruktory a destruktory volají automaticky, máme jistotu, že se kód vždy provede. Můžeme si tedy v konstruktoru alokovat paměť a v destruktoru ji zase smazat. Pro příklad si vezmeme naši arénu, ve které jsou aktuálně dva bojovníci. Řekněme, že chceme zadat počet bojovníků v parametru - musíme si dynamicky vytvořit pole bojovníků. Soubor Arena.h upravíme následovně:

#ifndef __ARENA_H_
#define __ARENA_H_
#include "Hrac.h"

class Arena
{
public:
    Hrac** hraci;
    int pocet_hracu;
    Arena(int _pocet_hracu); // byl změněn název parametru
    ~Arena();
};
#endif

Dvou hvězdiček se nebojte - je to pole ukazatelů na Hrac (nemůžeme vytvořit pouze pole hráčů, protože nemáme výchozí=bezpa­rametrický konstruktor, který je potřeba). V konstruktoru toto pole alokujeme, podle počtu hráčů se zeptáme na jména a hráče vytvoříme. V destruktoru poté provedeme opačnou operaci a vše smažeme. Na kód se můžete podívat:

Arena.cpp

#include <iostream>
#include "Arena.h"

using namespace std;

Arena::Arena(int _pocet_hracu)
{
    pocet_hracu = _pocet_hracu; // uložení počtu hráčů
    hraci = new Hrac*[pocet_hracu]; // vytvoření pole pro hráče
    for (int i = 0; i < pocet_hracu; i++)
    {
        string jmeno;
        cout << "Zadejte jmeno hrace: ";
        cin >> jmeno;
        hraci[i] = new Hrac(jmeno); // vytvoření hráče
    }
}

Arena::~Arena()
{
    for (int i = 0; i < pocet_hracu; i++)
        delete hraci[i]; // mazání hráčů
    delete[] hraci; // mazání pole
    hraci = NULL;
}

Pokud bychom paměť nemazali, zůstávala by alokována a neměli bychom se k ní jak dostat (neexistoval by na ni ukazatel) a nebylo by ji možné smazat tedy ani později. Pokud by probíhalo vytváření instancí například v cyklu, potom by program začal konzumovat stále více a více RAM, dokud by ji nezabral celou (a mít řádově gigabajty RAM pro takovou malou aplikaci je již poněkud zvláštní). Jestliže není volná RAM paměť a program požádá o další, operační systém už nemá co přidělit a aplikaci shodí. Proto, pokud se vám stává, že aplikace po nějaké době spadne, zkuste si zkontrolovat, kolik zabírá místa v paměti a pokud se toto místo neustále zvětšuje, zřejmě někde neuvolňujete paměť - tzv. memory leak.

main.cpp

Tím máme hotovou arénu a můžeme ji použít v main.cpp:

#include <iostream>
#include "Kostka.h"
#include "Arena.h"

using namespace std;


int main()
{
    Kostka kostka;
    for (int i = 0; i < 10; i++)
        cout << kostka.hod() << " ";
    cout << endl;

    Arena arena(4);
    cin.get();
    return 0;
}

Výsledek:

Konzolová aplikace
Volani konstruktoru s parametrem
Volani bezparametrickeho konstruktoru
2 6 1 2 1 6 2 3 1 4
Zadejte jmeno hrace: Pavel
Zadejte jmeno hrace: Karel
Zadejte jmeno hrace: Zdenek
Zadejte jmeno hrace: Lukas
Volani destruktoru pro kostku s 6 stenami

Vše funguje podle našich představ. Alokování a uvolňování paměti je nejčastější věc, která se v konstruktorech a destruktorech provádí, proto doporučuji si poslední ukázku pořádně projít a pochopit, jak to vlastně funguje.

To je pro tuto lekci vše.

V následujícím cvičení, Řešené úlohy k 3. a 4. lekci OOP v C++, si procvičíme nabyté zkušenosti z předchozích lekcí.


 

Měl jsi s čímkoli problém? Stáhni si vzorovou aplikaci níže a porovnej ji se svým projektem, chybu tak snadno najdeš.

Stáhnout

Stažením následujícího souboru souhlasíš s licenčními podmínkami

Staženo 101x (7.21 kB)
Aplikace je včetně zdrojových kódů v jazyce C++

 

Předchozí článek
Hrací kostka v C++ a konstruktory
Všechny články v sekci
Objektově orientované programování v C++
Přeskočit článek
(nedoporučujeme)
Řešené úlohy k 3. a 4. lekci OOP v C++
Článek pro vás napsal Patrik Valkovič
Avatar
Uživatelské hodnocení:
34 hlasů
Věnuji se programování v C++ a C#. Kromě toho také programuji v PHP (Nette) a JavaScriptu (NodeJS).
Aktivity