Dnes se podíváme na velmi užitečný koncept zvaný RAII, který usnadňuje správu prostředků a vede ke korektnějšímu kódu bez zbytečných duplicit.
Představte si následující funkci.
void foo() { Resource *r = getResource(); // Vrací dynamicky alokovaný objekt. // ... delete r; return; // ... delete r; return; // ... delete r; return; }
Získáme si v ní dynamicky alokovaný objekt r
typu Resource
, se kterým pracujeme. Před návratem z funkce je poté potřeba objekt korektně dealokovat. Vlivem větvení je však míst návratu z funkce celá řada a tak se dealokace vyskytuje vícekrát.
r
. Pokud bychom v budoucnu funkci rozšířili o další návrat, tak se na to velmi snadno zapomene.r
neprovede, protože se zbylý kód ve funkci od místa vyhození výjimky nevykoná.delete r
). Pokud dojde ke změně způsobu správy onoho prostředku, tak je potřeba zaručit, že budou změněna všechna místa, kde se delete r
provádí. Pokud bychom ve funkci pracovali s více takovými objekty, tak množství duplikovaného kódu ještě více naroste.r
voláme delete
, tak u volání getResource()
napíšeme komentář. Pokud bychom se mohli použití komentáře vyhnout, aniž by utrpěla čitelnost kódu, tak by to bylo plus (komentáře mají své nevýhody, např. že oproti kódu snadno zastarávají).Využijeme koncept zvaný RAII, což je zkratka z anglického Resource Acquisition Is Initialization. Funguje to tak, že spravovaný zdroj umístíme při inicializaci do lokálního objektu, který se ve svém destruktoru automaticky postará o jeho korektní uvolnění.
V našem případě, kdy se jedná o práci s dynamicky alokovanou pamětí využijeme std::unique_ptr
. Upravený kód bude pak vypadat takto:
void foo() { std::unique_ptr<Resource> r(getResource()); // ... return; // ... return; // ... return; }
Zaručíme si tak automatické uvolnění paměti, kód je nyní bezpečný z hlediska výjimek (destruktor od r
se zavolá i v případě, kdy dojde uvnitř foo()
k vyhození výjimky) a zmizel duplicitní kód i komentář.
Dále, pokud funkce getResource()
pochází od nás, tak bychom ji měli taktéž upravit. Konkrétně bychom měli změnit
Resource *getResource();
na
std::unique_ptr<Resource> getResource();
Tím explicitně říkáme, že volající getResource()
se stává vlastníkem onoho ukazatele. To nebylo dříve ze signatury funkce zřejmé (maximálně z komentáře, ale ten se snadno opomine).
try-finally
. Typickým příkladem je Java před verzí 7. C++ tuto konstrukci nemá (pouze try-catch
), což ale tolik nevadí, protože má RAII, jež může přinést ve výsledku lepší kód (kratší a čitelnější). Pokud byste ale chtěli, tak od C++11 si můžete try-finally
emulovat přes RAII a lambda funkce.with
. V Javě lze od verze 7 použít příkaz try-with-resources
.Aktualizace 19.10.2015: Doporučuji se podívat na tuto skvělou přednášku z CppConu, kde Andrei Alexandrescu využívá RAII pro zkvalitnění a zčitelnění kódu.
Přidat komentář