Search
Na minulém cvičení jsme pracovali s dynamicky alokovanými poli a vytvořili si část vlastního vektoru. Dnes si projdeme třídy, jejich životní cyklus a uděláme z našeho vektoru třídu.
Objekt může vzniknout dvěma způsoby. První je deklarací proměnné jeho typu, kdy objekt vznikne ve chvíli provádění daného řádku. Druhý způsob vzniku objektu je volání operátoru new nebo new[], kdy objekt vznikne po provedení alokace paměti.
new
new[]
Při vzniku objektu se vždy zavolá konstruktor. Konstruktorů může být více; který se zavolá záleží na způsobu vytvoření objektu, například prostá deklarace zavolá tzv. základní kostruktor.
#include <vector> int main() { std::vector<int> vec1; // vec1 začne existovat při provedení tohoto řádku a je použít tzv. základní konstruktor std::vector<int> vec2(2); // vec2 začne existovat při provedení tohoto řádku a je použit konstruktor který bere 1 číselný argument. }
K zániku objektu může dojít také dvěma způsoby. První je, že proměnná daného typu tzv. opustí scope, neboli přestane být platná. Druhý je, že je na ukazateli volán operátor delete nebo delete[], kdy nejdříve zanikne objekt a pak je systému vrácena dynamicky alokovaná paměť, kterou objekt obýval.
delete
delete[]
Při zániku objektu se vždy volá destruktor. Třída může mít destruktor pouze jeden.
#include <vector> int main() { std::vector<int> vec1; // vec1 začne existovat při provedení tohoto řádku a je použít tzv. základní konstruktor std::vector<int> vec2(2); // vec2 začne existovat při provedení tohoto řádku a je použit konstruktor který bere 1 číselný argument. } // zde zanikne vec2 i vec1 a je zavolán jejich destruktor.
Třídy jsou v C++ velmi podobné strukturám. Pokud vezmeme definici vektoru z minulého cvičení
struct vector { double* data = nullptr; size_t capacity = 0; size_t size = 0; };
struct
class
public:
class vector { public: double* data = nullptr; size_t capacity = 0; size_t size = 0; };
Nyní by všechno mělo fungovat jako předtím.
Vyzkoušíme si psaní metod pro třídu vector. Stáhněte si příklad se strukturou vector z minulého cvičení, rozdělený do několika souborů.
vector
Výchozí kód
Příklad zkompilujte a pokračujte podle následujících instrukcí:
public
dispose
reserve
push_back
pop_back
size
capacity
clear
at
resize
m_data
nullptr
m_capacity
m_size
size_t
double
private
Řešení
Pokud jsme dynamicky alokovali paměť, je potřeba ji i dealokovat. V minulém cvičení jsme používali new nebo new[] k alokaci paměti a delete nebo delete[] k dealokaci. V tomto cvičení jsme vytvořili třídu vector, která se za nás stará o pole čísel typu double. Předlohou pro náš vector je std::vector ze standardní knihovny, který se umí starat o pole různých typů.
std::vector
Ve standardní knihovně také najdeme třídu unique_ptr, která se stará o ukazatel na alokovanou paměť. Přitom rozlišuje mezi tím, zda-li vlastní ukazatel na jednotlivý objekt, nebo na pole. Důležité vlastnosti třídy unique_ptr jsou:
unique_ptr
unique_ptr<T>
unique_ptr<T[]>
Pokud tedy vložíme ukazatel do konstruktoru objektu unique_ptr, automaticky se nám postará o správné uvolnění paměti.
Mějme následující příklad:
#include <iostream> class my_class { public: my_class() { std::cout << "Hello\n"; } ~my_class() { std::cout << "Bye Bye\n"; } void stuff() { std::cout << "Stuff\n"; } }; int main() { auto* ptr = new my_class; auto* ptr2 = new my_class; ptr->stuff(); ptr2->stuff(); }
Když tento kód spustíme, ani jeden z objektů typu my_class se nesmaže. Tomu zabráníme pomocí unique_ptr tak, že ukazatele vložíme do konstruktoru unique_ptr, nejlépe ihned z operátoru new.
my_class
#include <memory> // pro unique_ptr ... int main() { std::unique_ptr<my_class> ptr(new my_class); std::unique_ptr<my_class> ptr2(new my_class); ptr->stuff(); ptr2->stuff(); }
Všimněte si, že není potřeba měnit způsob volání metody stuff. Je to proto, že unique_ptr je tzv. chytrý ukazatel (smart pointer), a díky tomu operace ptr->něco a *ptr mají stejný efekt, jako předtím.
stuff
ptr->něco
*ptr
Díky unique_ptr se teď oba objekty správně smažou. Pomocí funkce std::make_unique lze navíc dosáhnout poněkud kompaktnějšího zápisu:
std::make_unique
int main() { auto ptr = std::make_unique<my_class>(); auto ptr2 = std::make_unique<my_class>(); ptr->stuff(); ptr2->stuff(); }
Jako parametry funkce make_unique jsou použity jako parametry konstruktoru objektu my_class.
make_unique
Následující kód má v sobě několik chyb. Pokuste se je najít a opravit, nejdřív bez použití komplexních typů a poté za použití std::unique_ptr.
std::unique_ptr
#include <iostream> #include <algorithm> #include <sstream> void nacti_a_secti_cisla(std::istream& in, std::ostream& out) { int how_many; in >> how_many; if (!in.good()) { return; } int* numbers = new int[how_many]; int sum = 0; for (int n = 0; n < how_many; ++n) { in >> numbers[n]; sum += numbers[n]; } if (in.fail()) { return; } double average = sum / static_cast<double>(how_many); std::sort(numbers, numbers + how_many); int median = numbers[how_many / 2]; out << "suma zadanych cisel: " << sum << '\n'; out << "prumer zadanych cisel: " << average << '\n'; out << "median zadanych cisel: " << median << '\n'; }
Zde je funkce main s pár testy.
main
int main() { std::stringstream sok("3 1 2 3"); std::stringstream seof("3 1 2"); std::stringstream sfail("3 1 2 n"); nacti_a_secti_cisla(sok, std::cout); nacti_a_secti_cisla(seof, std::cout); nacti_a_secti_cisla(sfail, std::cout); }
Třídu unique_ptr můžeme také zakomponovat do našeho návrhu třídy vector. Výhodou bude, že odpadne nutnost vytvářet vlastní destruktor. Protože varianta unique_ptr pro pole poskytuje operátor indexace pole [], bude potřeba překvapivě málo úprav v našem stávajícím kódu.
[]
resize_array
std::unique_ptr<double[]>&
.get()
.swap()
std::unique_ptr<double[]>