PB161 Programování v jazyce C++ Přednáška 5 Práce s pamětí Princip RAII Lehký úvod do výjimek Nikola Beneš 16. října 2018 PB161 přednáška 5: práce s pamětí, princip RAII, lehce výjimky 16. října 2018 1 / 28 Přetypování (cast) Co už jste (možná) pochopili (-Wold-style-cast) v moderním C++ nechceme používat Céčkové přetypování (typ)hodnota divná syntax, nejasná priorita; není typově bezpečné Jaké máme možnosti v C++? static_cast(hodnota) základní přetypování; většinou bezpečné const_cast(hodnota) umožňuje porušit const jen pokud skutečně víte, co děláte; pozor na nedefinované chování reinterpret_cast(hodnota) donutí kompilátor dívat se na kus paměti jako na jiný typ to velmi často není to, co chcete má občas použití v nízkoúrovňovém kódu (práce s binárními soubory) dynamic_cast(hodnota) použití při dědičnosti; uvidíme později PB161 přednáška 5: práce s pamětí, princip RAII, lehce výjimky 16. října 2018 2 / 28 Přetypování (cast) Co už jste (možná) pochopili (-Wold-style-cast) v moderním C++ nechceme používat Céčkové přetypování (typ)hodnota divná syntax, nejasná priorita; není typově bezpečné Jaké máme možnosti v C++? static_cast(hodnota) základní přetypování; většinou bezpečné const_cast(hodnota) umožňuje porušit const jen pokud skutečně víte, co děláte; pozor na nedefinované chování reinterpret_cast(hodnota) donutí kompilátor dívat se na kus paměti jako na jiný typ to velmi často není to, co chcete má občas použití v nízkoúrovňovém kódu (práce s binárními soubory) dynamic_cast(hodnota) použití při dědičnosti; uvidíme později Nepoužívejte, pokud vám to explicitně nepovolíme PB161 přednáška 5: práce s pamětí, princip RAII, lehce výjimky 16. října 2018 2 / 28 Práce s pamětí PB161 přednáška 5: práce s pamětí, princip RAII, lehce výjimky 16. října 2018 3 / 28 Připomenutí Ukazatel proměnná, která drží adresu v paměti aritmetika ukazatelů (přičtení čísla k ukazateli, rozdíl ukazatelů) rozdíl mezi const int* a int* const a případně const int* const speciální ukazatel nullptr (od C++11) dříve NULL – nepoužívat v novém kódu! PB161 přednáška 5: práce s pamětí, princip RAII, lehce výjimky 16. října 2018 4 / 28 Alokace paměti – znáte z C Alokace na zásobníku (stack) lokální proměnné; automatické uvolnění paměti na konci bloku (v C++ navíc automatické volání destruktorů) PB161 přednáška 5: práce s pamětí, princip RAII, lehce výjimky 16. října 2018 5 / 28 Alokace paměti – znáte z C Alokace na zásobníku (stack) lokální proměnné; automatické uvolnění paměti na konci bloku (v C++ navíc automatické volání destruktorů) Statická alokace globální proměnné, statické proměnné ve funkcích (v C++ navíc statické atributy – uvidíme později) alokace před spuštěním programu inicializace před začátkem funkce main (složitější) dealokace po skončení programu (v C++ navíc volání destruktorů) PB161 přednáška 5: práce s pamětí, princip RAII, lehce výjimky 16. října 2018 5 / 28 Alokace paměti – znáte z C Alokace na zásobníku (stack) lokální proměnné; automatické uvolnění paměti na konci bloku (v C++ navíc automatické volání destruktorů) Statická alokace globální proměnné, statické proměnné ve funkcích (v C++ navíc statické atributy – uvidíme později) alokace před spuštěním programu inicializace před začátkem funkce main (složitější) dealokace po skončení programu (v C++ navíc volání destruktorů) Dynamická alokace na haldě (heap) explicitní žádost o paměť explicitní uvolnění paměti znáte z C: malloc/free (v C++ nepoužíváme) nebezpečné: žádná typová kontrola, bez inicializace PB161 přednáška 5: práce s pamětí, princip RAII, lehce výjimky 16. října 2018 5 / 28 Práce s pamětí v C++ Nízkoúrovňová (ruční) operátory new a delete new alokuje paměť na haldě a zavolá konstruktor delete zavolá destruktor a dealokuje paměť moderní C++: příliš nepoužívat, jen v nutných případech nicméně je dobré vědět, jak funguje PB161 přednáška 5: práce s pamětí, princip RAII, lehce výjimky 16. října 2018 6 / 28 Práce s pamětí v C++ Nízkoúrovňová (ruční) operátory new a delete new alokuje paměť na haldě a zavolá konstruktor delete zavolá destruktor a dealokuje paměť moderní C++: příliš nepoužívat, jen v nutných případech nicméně je dobré vědět, jak funguje Vysokoúrovňová (automatická) využití standardní knihovny, příp. jiných knihoven (boost apod.) už jsme vlastně viděli vector, string a jiné kontejnery vnitřně alokují a dealokují paměť na haldě jiné možnosti: chytré ukazatele automatické uvolnění paměti unique_ptr (od C++11 ve standardní knihovně) a jiné PB161 přednáška 5: práce s pamětí, princip RAII, lehce výjimky 16. října 2018 6 / 28 Nízkoúrovňová správa paměti Dva způsoby alokace jeden objekt pole objektů int* ptr = new int; int* array = new int[20]; když se alokace nezdaří, vyhodí výjimku (o těch bude řeč později) není proto třeba kontrolovat na nullptr PB161 přednáška 5: práce s pamětí, princip RAII, lehce výjimky 16. října 2018 7 / 28 Nízkoúrovňová správa paměti Dva způsoby alokace jeden objekt pole objektů int* ptr = new int; int* array = new int[20]; když se alokace nezdaří, vyhodí výjimku (o těch bude řeč později) není proto třeba kontrolovat na nullptr Odpovídající způsoby dealokace lecture05_01.cpp, lecture05_02.cpp delete ptr; delete [] array; musí odpovídat použitému new nehlídá kompilátor (někdy ovšem umí dát warning)! Proč? PB161 přednáška 5: práce s pamětí, princip RAII, lehce výjimky 16. října 2018 7 / 28 Nízkoúrovňová správa paměti (pokr.) Inicializace alokovaných objektů funguje podobně jako inicializace lokálních objektů int* ptr1 = new int; // neinicializovaná paměť int* ptr2 = new int(17); // paměť inicializovaná na 17 int* ptr3 = new int(); // paměť inicializovaná na 0 PB161 přednáška 5: práce s pamětí, princip RAII, lehce výjimky 16. října 2018 8 / 28 Nízkoúrovňová správa paměti (pokr.) Inicializace alokovaných objektů funguje podobně jako inicializace lokálních objektů int* ptr1 = new int; // neinicializovaná paměť int* ptr2 = new int(17); // paměť inicializovaná na 17 int* ptr3 = new int(); // paměť inicializovaná na 0 Obj* ptr4 = new Obj; // zavolá se konstruktor Obj::Obj() Obj* ptr5 = new Obj(3, 5); // konstruktor Obj::Obj(int, int) PB161 přednáška 5: práce s pamětí, princip RAII, lehce výjimky 16. října 2018 8 / 28 Nízkoúrovňová správa paměti (pokr.) Inicializace alokovaných objektů funguje podobně jako inicializace lokálních objektů int* ptr1 = new int; // neinicializovaná paměť int* ptr2 = new int(17); // paměť inicializovaná na 17 int* ptr3 = new int(); // paměť inicializovaná na 0 Obj* ptr4 = new Obj; // zavolá se konstruktor Obj::Obj() Obj* ptr5 = new Obj(3, 5); // konstruktor Obj::Obj(int, int) Alokace a inicializace polí lecture05_03.cpp int* arr1 = new int[200]; // neinicializovaná paměť int* arr2 = new int[200](); // paměť inicializovaná na 0 PB161 přednáška 5: práce s pamětí, princip RAII, lehce výjimky 16. října 2018 8 / 28 Nízkoúrovňová správa paměti (pokr.) Inicializace alokovaných objektů funguje podobně jako inicializace lokálních objektů int* ptr1 = new int; // neinicializovaná paměť int* ptr2 = new int(17); // paměť inicializovaná na 17 int* ptr3 = new int(); // paměť inicializovaná na 0 Obj* ptr4 = new Obj; // zavolá se konstruktor Obj::Obj() Obj* ptr5 = new Obj(3, 5); // konstruktor Obj::Obj(int, int) Alokace a inicializace polí lecture05_03.cpp int* arr1 = new int[200]; // neinicializovaná paměť int* arr2 = new int[200](); // paměť inicializovaná na 0 Obj* arr3 = new Obj[200]; // zavolá se konstruktor Obj::Obj() samozřejmě funguje i inicializace pomocí {} PB161 přednáška 5: práce s pamětí, princip RAII, lehce výjimky 16. října 2018 8 / 28 Nízkoúrovňová správa paměti (pokr.) Problémy správy paměti (některé znáte z C) PB161 přednáška 5: práce s pamětí, princip RAII, lehce výjimky 16. října 2018 9 / 28 Nízkoúrovňová správa paměti (pokr.) Problémy správy paměti (některé znáte z C) memory leak (alokovaná paměť, na kterou již nemáme ukazatel) zápis do/čtení z nealokované paměti čtení z neinicializované paměti volání delete na špatný ukazatel volání delete místo delete [] a naopak volání free na paměť alokovanou new, nebo delete/delete[] na pamět alokovanou malloc PB161 přednáška 5: práce s pamětí, princip RAII, lehce výjimky 16. října 2018 9 / 28 Nízkoúrovňová správa paměti (pokr.) Problémy správy paměti (některé znáte z C) memory leak (alokovaná paměť, na kterou již nemáme ukazatel) zápis do/čtení z nealokované paměti čtení z neinicializované paměti volání delete na špatný ukazatel volání delete místo delete [] a naopak volání free na paměť alokovanou new, nebo delete/delete[] na pamět alokovanou malloc v C++ nikdy nepoužíváme free/malloc (leda by to vyžadovala použitá C knihovna)! PB161 přednáška 5: práce s pamětí, princip RAII, lehce výjimky 16. října 2018 9 / 28 Nízkoúrovňová správa paměti (pokr.) Problémy správy paměti (některé znáte z C) memory leak (alokovaná paměť, na kterou již nemáme ukazatel) zápis do/čtení z nealokované paměti čtení z neinicializované paměti volání delete na špatný ukazatel volání delete místo delete [] a naopak volání free na paměť alokovanou new, nebo delete/delete[] na pamět alokovanou malloc v C++ nikdy nepoužíváme free/malloc (leda by to vyžadovala použitá C knihovna)! Motivace pro chytré ukazatele chceme ukazatel, který sám provede delete, když je potřeba PB161 přednáška 5: práce s pamětí, princip RAII, lehce výjimky 16. října 2018 9 / 28 Vysokoúrovňová správa paměti Chytré ukazatele několik druhů ve standardní knihovně (od C++11), příp. v jiných knihovnách (boost) v tomto předmětu si ukážeme pouze jeden PB161 přednáška 5: práce s pamětí, princip RAII, lehce výjimky 16. října 2018 10 / 28 Vysokoúrovňová správa paměti Chytré ukazatele několik druhů ve standardní knihovně (od C++11), příp. v jiných knihovnách (boost) v tomto předmětu si ukážeme pouze jeden std::unique_ptr nejjednodušší, ale pokrývá většinu potřeb má nulovou režii (overhead) za běhu, na rozdíl od jiných použitelný pro jednotlivé objekty i pole PB161 přednáška 5: práce s pamětí, princip RAII, lehce výjimky 16. října 2018 10 / 28 Vysokoúrovňová správa paměti Chytré ukazatele několik druhů ve standardní knihovně (od C++11), příp. v jiných knihovnách (boost) v tomto předmětu si ukážeme pouze jeden std::unique_ptr nejjednodušší, ale pokrývá většinu potřeb má nulovou režii (overhead) za běhu, na rozdíl od jiných použitelný pro jednotlivé objekty i pole základní princip: objekt, který uvnitř drží ukazatel (koncept vlastnictví) na konci života objektu se zavolá delete na vlastněný ukazatel (tedy zavolá destruktor a dealokuje) vlastnictví se nedá sdílet (proto unique) ale může se explicitně předat PB161 přednáška 5: práce s pamětí, princip RAII, lehce výjimky 16. října 2018 10 / 28 Vysokoúrovňová správa paměti (pokr.) Základní použití std::unique_ptr lecture05_04.cpp, lecture05_05.cpp alokace pokud neinicializujeme, je automaticky nullptr std::unique_ptr ptr(new Object(params)); // v C++11 // od C++14 – používejte raději takto auto ptr = std::make_unique(params); PB161 přednáška 5: práce s pamětí, princip RAII, lehce výjimky 16. října 2018 11 / 28 Vysokoúrovňová správa paměti (pokr.) Základní použití std::unique_ptr lecture05_04.cpp, lecture05_05.cpp alokace pokud neinicializujeme, je automaticky nullptr std::unique_ptr ptr(new Object(params)); // v C++11 // od C++14 – používejte raději takto auto ptr = std::make_unique(params); přístup k objektu – stejně jak u klasických ukazatelů (*, ->) pozor, nedefinované chování, pokud by byl ptr == nullptr ptr->method(); function(*ptr); PB161 přednáška 5: práce s pamětí, princip RAII, lehce výjimky 16. října 2018 11 / 28 Vysokoúrovňová správa paměti (pokr.) Základní použití std::unique_ptr lecture05_04.cpp, lecture05_05.cpp alokace pokud neinicializujeme, je automaticky nullptr std::unique_ptr ptr(new Object(params)); // v C++11 // od C++14 – používejte raději takto auto ptr = std::make_unique(params); přístup k objektu – stejně jak u klasických ukazatelů (*, ->) pozor, nedefinované chování, pokud by byl ptr == nullptr ptr->method(); function(*ptr); dealokace, zavolání destruktoru objektu lokální unique_ptr: automaticky, na konci bloku unique_ptr jako atribut: automaticky po destruktoru hlavního objektu explicitně: zavoláním metody reset() PB161 přednáška 5: práce s pamětí, princip RAII, lehce výjimky 16. října 2018 11 / 28 Vysokoúrovňová správa paměti (pokr.) lecture05_06.cpp zjištění, zda unique_ptr obsahuje nějaký ukazatel if (ptr) { ... } PB161 přednáška 5: práce s pamětí, princip RAII, lehce výjimky 16. října 2018 12 / 28 Vysokoúrovňová správa paměti (pokr.) lecture05_06.cpp zjištění, zda unique_ptr obsahuje nějaký ukazatel if (ptr) { ... } přímý přístup ke spravovanému ukazateli (k čemu je to dobré?) Object* rawPtr = ptr.get(); // ptr je stále vlastníkem ukazatele PB161 přednáška 5: práce s pamětí, princip RAII, lehce výjimky 16. října 2018 12 / 28 Vysokoúrovňová správa paměti (pokr.) lecture05_06.cpp zjištění, zda unique_ptr obsahuje nějaký ukazatel if (ptr) { ... } přímý přístup ke spravovanému ukazateli (k čemu je to dobré?) Object* rawPtr = ptr.get(); // ptr je stále vlastníkem ukazatele vzdání se vlastnictví (k čemu je to dobré?) // tohle raději nedělejte! Object* rawPtr = ptr.release(); // ptr už není vlastníkem ukazatele, který je nyní uložen // v rawPtr, a je třeba jej uvolnit ručně! PB161 přednáška 5: práce s pamětí, princip RAII, lehce výjimky 16. října 2018 12 / 28 Vysokoúrovňová správa paměti (pokr.) předávání vlastnictví jinému unique_ptr unique_ptr se nedá kopírovat! // tohle nebude fungovat std::unique_ptr newPtr = ptr; // chyba! PB161 přednáška 5: práce s pamětí, princip RAII, lehce výjimky 16. října 2018 13 / 28 Vysokoúrovňová správa paměti (pokr.) předávání vlastnictví jinému unique_ptr unique_ptr se nedá kopírovat! // tohle nebude fungovat std::unique_ptr newPtr = ptr; // chyba! unique_ptr se umí tzv. přesouvat (move) využívá rvalue semantics, mimo záběr předmětu std::unique_ptr newPtr = std::move(ptr); // newPtr teď vlastní ukazatel, který předtím vlastnil ptr // ptr teď nevlastní nic, je tedy ekvivalentní nullptr PB161 přednáška 5: práce s pamětí, princip RAII, lehce výjimky 16. října 2018 13 / 28 Vysokoúrovňová správa paměti (pokr.) předávání vlastnictví jinému unique_ptr unique_ptr se nedá kopírovat! // tohle nebude fungovat std::unique_ptr newPtr = ptr; // chyba! unique_ptr se umí tzv. přesouvat (move) využívá rvalue semantics, mimo záběr předmětu std::unique_ptr newPtr = std::move(ptr); // newPtr teď vlastní ukazatel, který předtím vlastnil ptr // ptr teď nevlastní nic, je tedy ekvivalentní nullptr funguje nejen při inicializaci, ale i při přiřazení lecture05_07.cpp auto ptrA = std::make_unique(); auto ptrB = std::make_unique(); ptrA = std::move(ptrB); // kdo co vlastní teď? PB161 přednáška 5: práce s pamětí, princip RAII, lehce výjimky 16. října 2018 13 / 28 Vysokoúrovňová správa paměti (pokr.) použití pro alokaci polí // v C++11 std::unique_ptr ptr(new Object[size]); // od C++14 – používejte raději takto auto ptr = std::make_unique(size); // alokuje paměť pro size objektů // a všechny inicializuje bezparametrickým konstruktorem PB161 přednáška 5: práce s pamětí, princip RAII, lehce výjimky 16. října 2018 14 / 28 Vysokoúrovňová správa paměti (pokr.) použití pro alokaci polí // v C++11 std::unique_ptr ptr(new Object[size]); // od C++14 – používejte raději takto auto ptr = std::make_unique(size); // alokuje paměť pro size objektů // a všechny inicializuje bezparametrickým konstruktorem použití pro alokací polí primitivních typů lecture05_08.cpp auto ptr = std::make_unique(size); // alokuje paměť pro size intů // a všechny inicializuje na 0 inicializuje tedy jako new int[size]() PB161 přednáška 5: práce s pamětí, princip RAII, lehce výjimky 16. října 2018 14 / 28 Vysokoúrovňová správa paměti (pokr.) Jak správně pracovat s ukazateli v moderním C++ jasně si rozmyslete, kdo bude vlastníkem ukazatele ten pak má unique_ptr ukazující na daný objekt ostatní (ne-vlastníci) smí mít klasický (surový, raw) ukazatel na tentýž objekt (nebo lépe referenci) je třeba zaručit, aby vlastník ukazatele přežil všechny ne-vlastníky, kteří ukazovaný objekt používají Jak předávat unique_ptr do funkce? lecture05_09.cpp PB161 přednáška 5: práce s pamětí, princip RAII, lehce výjimky 16. října 2018 15 / 28 Vysokoúrovňová správa paměti (pokr.) Jak správně pracovat s ukazateli v moderním C++ jasně si rozmyslete, kdo bude vlastníkem ukazatele ten pak má unique_ptr ukazující na daný objekt ostatní (ne-vlastníci) smí mít klasický (surový, raw) ukazatel na tentýž objekt (nebo lépe referenci) je třeba zaručit, aby vlastník ukazatele přežil všechny ne-vlastníky, kteří ukazovaný objekt používají Jak předávat unique_ptr do funkce? lecture05_09.cpp hodnotou typu unique_ptr: volající pak musí použít std::move a tím se vzdává vlastnictví ukazatele referencí: volaná funkce může sebrat vlastnictví (nedoporučované) const referencí: v podstatě OK, ale volaná funkce může modifikovat odkazovaný objekt (je to jakoby Object* const) surový ukazatel: může být Object * nebo const Object * úplně nejlépe – referencí na Object: volající musí zajistit, že není nullptr PB161 přednáška 5: práce s pamětí, princip RAII, lehce výjimky 16. října 2018 15 / 28 Vysokoúrovňová správa paměti (pokr.) Datové struktury a unique_ptr lecture05_10.cpp je třeba rozmyslet strukturu vlastnictví PB161 přednáška 5: práce s pamětí, princip RAII, lehce výjimky 16. října 2018 16 / 28 Vysokoúrovňová správa paměti (pokr.) Datové struktury a unique_ptr lecture05_10.cpp je třeba rozmyslet strukturu vlastnictví není možné mít ve vlastnictví cykly (proč?) PB161 přednáška 5: práce s pamětí, princip RAII, lehce výjimky 16. října 2018 16 / 28 Vysokoúrovňová správa paměti (pokr.) Datové struktury a unique_ptr lecture05_10.cpp je třeba rozmyslet strukturu vlastnictví není možné mít ve vlastnictví cykly (proč?) není možné, aby dva vlastnili stejný objekt PB161 přednáška 5: práce s pamětí, princip RAII, lehce výjimky 16. října 2018 16 / 28 Vysokoúrovňová správa paměti (pokr.) Datové struktury a unique_ptr lecture05_10.cpp je třeba rozmyslet strukturu vlastnictví není možné mít ve vlastnictví cykly (proč?) není možné, aby dva vlastnili stejný objekt oboustranně zřetězený seznam (v prvcích) next je unique_ptr, prev je ukazatel (v seznamu) first je unique_ptr, last je ukazatel nebo naopak PB161 přednáška 5: práce s pamětí, princip RAII, lehce výjimky 16. října 2018 16 / 28 Vysokoúrovňová správa paměti (pokr.) Datové struktury a unique_ptr lecture05_10.cpp je třeba rozmyslet strukturu vlastnictví není možné mít ve vlastnictví cykly (proč?) není možné, aby dva vlastnili stejný objekt oboustranně zřetězený seznam (v prvcích) next je unique_ptr, prev je ukazatel (v seznamu) first je unique_ptr, last je ukazatel nebo naopak binární strom vztah rodič vlastní potomky left a right jsou unique_ptr, případný parent je ukazatel apod. PB161 přednáška 5: práce s pamětí, princip RAII, lehce výjimky 16. října 2018 16 / 28 Správa paměti v moderním C++ Doporučení používejte vysokoúrovňovou správu paměti (unique_ptr) nepoužívejte new a delete ale naučte se jim rozumět často je uvidíte v cizím kódu do funkcí předávejte objekty pokud možno referencí (toto doporučení už bylo!) předávejte unique_ptr hodnotou, pokud se chcete vzdát vlastnictví implementujete-li složitější datovou strukturu s ukazateli, rozmyslete si strukturu vlastnictví (kdo koho vlastní) PB161 přednáška 5: práce s pamětí, princip RAII, lehce výjimky 16. října 2018 17 / 28 Správa zdrojů – princip RAII ACQUIRE USE RELEASE PB161 přednáška 5: práce s pamětí, princip RAII, lehce výjimky 16. října 2018 18 / 28 Správa zdrojů Co to je zdroj? PB161 přednáška 5: práce s pamětí, princip RAII, lehce výjimky 16. října 2018 19 / 28 Správa zdrojů Co to je zdroj? něco, co se získá (acquire) potom se to používá (use) nakonec se to uvolní (release) Příklady zdrojů PB161 přednáška 5: práce s pamětí, princip RAII, lehce výjimky 16. října 2018 19 / 28 Správa zdrojů Co to je zdroj? něco, co se získá (acquire) potom se to používá (use) nakonec se to uvolní (release) Příklady zdrojů paměť na haldě PB161 přednáška 5: práce s pamětí, princip RAII, lehce výjimky 16. října 2018 19 / 28 Správa zdrojů Co to je zdroj? něco, co se získá (acquire) potom se to používá (use) nakonec se to uvolní (release) Příklady zdrojů paměť na haldě soubory síťová připojení zámky, mutexy, semafory, … apod. PB161 přednáška 5: práce s pamětí, princip RAII, lehce výjimky 16. října 2018 19 / 28 Správa zdrojů Co to je zdroj? něco, co se získá (acquire) potom se to používá (use) nakonec se to uvolní (release) Příklady zdrojů paměť na haldě soubory síťová připojení zámky, mutexy, semafory, … apod. v C++ vše funguje na stejném principu Různé jazyky často implementují automatickou správu paměti, ale ne automatickou správu zdrojů (v posledních letech se to trochu zlepšuje). C++ má automatickou správu zdrojů už skoro od svého počátku. PB161 přednáška 5: práce s pamětí, princip RAII, lehce výjimky 16. října 2018 19 / 28 Správa zdrojů v C++ Princip RAII – „Resource Acquisition Is Initialisation“ někdy též zváno scope-based resource management správa zdroje spjatá s životním cyklem objektu inicializace objektu: získání zdroje (acquire) destruktor: uvolnění zdroje (release) ideálně: jeden objekt spravuje jeden zdroj PB161 přednáška 5: práce s pamětí, princip RAII, lehce výjimky 16. října 2018 20 / 28 Správa zdrojů v C++ Princip RAII – „Resource Acquisition Is Initialisation“ někdy též zváno scope-based resource management správa zdroje spjatá s životním cyklem objektu inicializace objektu: získání zdroje (acquire) destruktor: uvolnění zdroje (release) ideálně: jeden objekt spravuje jeden zdroj Kde se používá RAII? skoro všude! string, vector, všechny kontejnery práce se soubory v C++ (později) chytré ukazatele: unique_ptr (C++11) zamykání, mutexy (C++11, nad rámec kurzu) PB161 přednáška 5: práce s pamětí, princip RAII, lehce výjimky 16. října 2018 20 / 28 Princip RAII Příklad – dynamické pole intů (pamatujete na Rule of Three?) lecture05_11.cpp class IntArray { size_t size; int* array; public: IntArray(size_t size) : size(size), array(new int[size]) {} ~IntArray() { delete [] array; } IntArray(const IntArray& other) : size(other.size), array(new int[size]) { std::copy(other.array, other.array + size, array); } IntArray& operator=(const IntArray& other) { // viz další slajdy } } PB161 přednáška 5: práce s pamětí, princip RAII, lehce výjimky 16. října 2018 21 / 28 Příklad – dynamické pole intů Jak implementovat přiřazovací operátor? IntArray& operator=(const IntArray& other) { delete [] array; size = other.size; array = new int[size]; std::copy(other.array, other.array + size, array); return *this; } kde je problém? PB161 přednáška 5: práce s pamětí, princip RAII, lehce výjimky 16. října 2018 22 / 28 Příklad – dynamické pole intů Jak implementovat přiřazovací operátor? IntArray& operator=(const IntArray& other) { delete [] array; size = other.size; array = new int[size]; std::copy(other.array, other.array + size, array); return *this; } kde je problém? sebe-přiřazení (co se stane, když napíšu x = x;?) jak řešit? PB161 přednáška 5: práce s pamětí, princip RAII, lehce výjimky 16. října 2018 22 / 28 Příklad – dynamické pole intů (pokr.) Kontrola sebe-přiřazení IntArray& operator=(const IntArray& other) { if (&other == this) return *this; delete [] array; size = other.size; array = new int[size]; std::copy(other.array, other.array + size, array); return *this; } optimalizace není třeba dělat delete [] a new [], pokud jsou velikosti stejné PB161 přednáška 5: práce s pamětí, princip RAII, lehce výjimky 16. října 2018 23 / 28 Příklad – dynamické pole intů (pokr.) Jiná možnost, tzv. copy-and-swap idiom void swap(IntArray& other) { using std::swap; swap(size, other.size); swap(array, other.array); } IntArray& operator=(IntArray other) { // HODNOTOU! swap(other); return *this; } co se děje? PB161 přednáška 5: práce s pamětí, princip RAII, lehce výjimky 16. října 2018 24 / 28 Příklad – dynamické pole intů (pokr.) Jiná možnost, tzv. copy-and-swap idiom void swap(IntArray& other) { using std::swap; swap(size, other.size); swap(array, other.array); } IntArray& operator=(IntArray other) { // HODNOTOU! swap(other); return *this; } co se děje? Rule of Three and a Half ke třem dříve zmíněným se přidá metoda swap copy-and-swap idiom silně doporučujeme! PB161 přednáška 5: práce s pamětí, princip RAII, lehce výjimky 16. října 2018 24 / 28 Princip RAII Jiné příklady (pro ilustraci) třída File konstruktor otevře soubor destruktor zavře soubor kopírování nejspíše zakážeme PB161 přednáška 5: práce s pamětí, princip RAII, lehce výjimky 16. října 2018 25 / 28 Princip RAII Jiné příklady (pro ilustraci) třída File konstruktor otevře soubor destruktor zavře soubor kopírování nejspíše zakážeme třída Mutex konstruktor zamkne mutex destruktor odemkne mutex kopírování opět zakážeme PB161 přednáška 5: práce s pamětí, princip RAII, lehce výjimky 16. října 2018 25 / 28 Princip RAII Jiné příklady (pro ilustraci) třída File konstruktor otevře soubor destruktor zavře soubor kopírování nejspíše zakážeme třída Mutex konstruktor zamkne mutex destruktor odemkne mutex kopírování opět zakážeme třída Texture konstruktor načte texturu ze souboru do paměti GPU destruktor uvolní paměť GPU kopírování: vytvoření nové textury PB161 přednáška 5: práce s pamětí, princip RAII, lehce výjimky 16. října 2018 25 / 28 Princip RAII v jiných jazycích Některé jazyky mají RAII C++, D, Ada, Rust umí plné RAII tak trochu: Perl, Python (CPython), PHP mají reference counting Správa zdrojů v jiných jazycích garbage collector: správa paměti, typicky neumožňuje správu zdrojů (není žádná záruka, kdy a jestli vůbec se zavolají destruktory) Java (od 1.7) synchronized try(Resource res = new Resource(...)) { ... } Python (od 2.5) with get_resource() as resource: C# using(Resource res = new Resource(...)) { ... } PB161 přednáška 5: práce s pamětí, princip RAII, lehce výjimky 16. října 2018 26 / 28 Lehký úvod do výjimek lecture05_12.cpp Výjimky způsob, jak řešit výjimečné situace (chyby) za běhu programu vyhození výjimky: throw zachycení výjimky: try { ... } catch(...) { ... } standardní výjimky v knihovně: testování v Catch2: REQUIRE_THROWS_AS(výraz, typ výjimky) apod. int main() { try { throw std::runtime_error("Something bad happened."); } catch (std::runtime_error& ex) { std::cout << ex.what() << '\n'; } } PB161 přednáška 5: práce s pamětí, princip RAII, lehce výjimky 16. října 2018 27 / 28 Lehký úvod do výjimek (pokr.) Pár pravidel (podrobněji se k nim dostaneme později) výjimkami řešte pouze výjimečné situace házejte hodnotou, chytejte referencí nevyhazujte výjimky z destruktorů vyhazujte výjimky z konstruktorů, pokud nedokážete smysluplně dokončit konstrukci objektu PB161 přednáška 5: práce s pamětí, princip RAII, lehce výjimky 16. října 2018 28 / 28 Lehký úvod do výjimek (pokr.) Pár pravidel (podrobněji se k nim dostaneme později) výjimkami řešte pouze výjimečné situace házejte hodnotou, chytejte referencí nevyhazujte výjimky z destruktorů vyhazujte výjimky z konstruktorů, pokud nedokážete smysluplně dokončit konstrukci objektu Vztah výjimek a RAII lecture05_13.cpp destrukory lokálních objektů se zavolají vždy při opuštění bloku to se týká i opuštění bloku při vyhození výjimky srovnejte použití std::unique_ptr a explicitního new a delete ve chvíli, kdy uvnitř bloku vznikne výjimka PB161 přednáška 5: práce s pamětí, princip RAII, lehce výjimky 16. října 2018 28 / 28