{{page>courses:b6b36pjc:styles#common&noheader&nofooter}} {{page>courses:b6b36pjc:styles#cviceni&noheader&nofooter}} ===== Cvičení 5: Životní cyklus objektů ===== 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. ==== Vznik ==== 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. 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 int main() { std::vector vec1; // vec1 začne existovat při provedení tohoto řádku a je použít tzv. základní konstruktor std::vector vec2(2); // vec2 začne existovat při provedení tohoto řádku a je použit konstruktor který bere 1 číselný argument. } ==== Zánik ==== 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. Při zániku objektu se vždy volá destruktor. Třída může mít destruktor pouze jeden. #include int main() { std::vector vec1; // vec1 začne existovat při provedení tohoto řádku a je použít tzv. základní konstruktor std::vector 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. ===== Od struktury ke třídě ===== 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; }; stačí zaměnit klíčové slovo ''struct'' za klíčové slovo ''class''. Pokud to uděláme, zjistíme, že naše staré funkce přestaly fungovat. To proto, že součásti třídy jsou soukromé (//private//). Pokud chceme mít přístup k částem třídy, třeba k metodám, i mimo třídu, pak musíme použít klíčové slovo ''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. ==== Metody třídy ==== 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ů. {{:courses:b6b36pjc:cviceni:cviceni_5_struktura.zip|Výchozí kód}} Příklad zkompilujte a pokračujte podle následujících instrukcí: * Změňte strukturu ''vector'' na třídu a změňte viditelnost jejích datových položek na ''public''. * Vytvořte destruktor třídy ''vector''. Přesuňte do něj kód z metody ''dispose'' a metodu ''dispose'' smažte. * Změňte funkce ''reserve'', ''push_back'', ''pop_back'', ''size'', ''capacity'', ''clear'', ''at'', a ''resize'' na metody třídy ''vector''. * Vytvořte základní (default) konstruktor třídy ''vector'', který nastaví ''m_data'' na ''nullptr'', ''m_capacity'' na 0 a ''m_size'' na 0. * Vytvořte konstruktor třídy ''vector'' se dvěma parametry: * 1. parametr typu ''size_t'' určuje velikost a kapacitu nového vektoru, * 2. parametr typu ''double'' určuje hodnotu, na kterou se mají nastavit všechny položky ve vektoru. * Změňte viditelnost položek ''m_data'', ''m_capacity'' a ''m_size'' na ''private''. * Zkuste zaměnit klíčové slovo ''class'' za ''struct''. Co se stane? {{:courses:b6b36pjc:cviceni:cviceni_5_trida.zip|Řešení}} ===== Správa prostředků pomocí unique_ptr ===== 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ů. 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: * Konstruktor očekává ukazatel. Říkáme, že tento ukazatel ''unique_ptr'' vlastní. * Destruktor smaže vlastněný ukazatel. * Pokud je typu ''unique_ptr'', konstruktor očekává ukazatel na jeden objekt a destruktor používá operátor ''delete''. * Pokud je typu ''unique_ptr'', konstruktor očekává ukazatel na pole a destruktor používá operátor ''delete[]''. Pokud tedy vložíme ukazatel do konstruktoru objektu ''unique_ptr'', automaticky se nám postará o správné uvolnění paměti. ==== Případ jednotlivého objektu ==== Mějme následující příklad: #include 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''. #include // pro unique_ptr ... int main() { std::unique_ptr ptr(new my_class); std::unique_ptr 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. 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: int main() { auto ptr = std::make_unique(); auto ptr2 = std::make_unique(); ptr->stuff(); ptr2->stuff(); } Jako parametry funkce ''make_unique'' jsou použity jako parametry konstruktoru objektu ''my_class''. ==== Případ pole ==== 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''. #include #include 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(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. #include #include int main() { std::stringstream sok("3 1 2 3"); std::stringstream seof("3 1 2"); std::stringstream sfail("3 1 2 n"); sfail.exceptions(std::ios_base::failbit); nacti_a_secti_cisla(sok, std::cout); nacti_a_secti_cisla(seof, std::cout); nacti_a_secti_cisla(sfail, std::cout); } ==== Vektor s unique_ptr ==== 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. * Vytvořte přetížení funkce ''resize_array'', které umí změnit velikost pole, které funkci poskytneme v parametru s typem ''std::unique_ptr&''. Budou se hodit následující metody třídy ''unique_ptr'': * Metoda ''.get()'' poskytne ukazatel, který je obsažen uvnitř ''unique_ptr''. * Metoda ''.swap()'' vzájemně prohodí pole dvou ''unique_ptr''. * Změňte typ položky ''m_data'' na ''std::unique_ptr''. * Odstraňte destruktor třídy ''vector''. {{:courses:b6b36pjc:cviceni:cviceni_5_unique_ptr.zip|Řešení}}