Na diskusních fórech se stále dokola opakuje tvrzení, že v C++ hrozí úniky paměti, že je na ně náchylné, případně že je pro programátora náročné psát kód bez úniků paměti a že právě proto je psaní kódu v C++ méně efektivní. Kdo toto tvrdí, je veleosel.
Sám tvůrce C++ Bjarne Stroustrup vysvětluje, jak správa paměti vzhledem k objektům funguje, nemá cenu vše opakovat. Stručně shrnuto, díky RAII se paměť uvolňuje automaticky, jakmile se proměnná dostane mimo lexikální rozsah platnosti, čímž je zajištěna korektkní správa paměti i v případě výjimek nebo jiného typu opuštění funkce někde zprostředka. Pokud se instance objektu nepředává referencí, typicky se použije move konstruktor (jeho použití si lze vynutit v případech, kdy by ho překladač nepoužil explicitně). Celá STL tyto konstruktury používá. Kdo se chce vyhnout move sémantice úplně, použije shared_ptr (nebo nějaký jiný xxx_ptr podle toho, co vlastně potřebuje) a k tomu make_shared, čímž opět přenechá správu paměti překladači a STL.
Výhoda GC spočívá v deterministické alokaci (v C++ můžete mít svůj alokátor, ale ten standardní deterministický není). Platíte za to ovšem nedeterministickým uvolňováním paměti (a hlavně vyšším footprintem). Opět lze namítnout, že existují deterministické GC, jenže ty se používají jen ve speciálních případech (typicky real-time systémech, i když tam se většinou GC nepoužívá vůbec), neboť mají spoustu jiných nevýhod.
Kdo stále ještě nechápe, nechť se prosím nevěnuje programování.
P.S. Některá rozšíření C(++) jdou ještě dále, co se týká činnosti překladače. V Objective-C(++) se díky ARC nemusíte starat o uvolňování, prostě jen alokujete. Navíc překladač inteligentně vyhazuje instrukce pro aktualizace čítače referencí všude tam, kde nejsou potřeba, a takových případů je většina (pokud to řeší knihovna bez podpory překladače, jako např. STL, není taková optimalizace možná). Velice podobnou optimalizaci ma C++/CX ve Windows 8, také jen alokujete (pomocí ref new) a instrukce pro správu čítače referencí generuje překladač jen tam, kde jsou nezbytné.
P.P.S. Je vhodné doplnit (v reakci na diskusi), že použití operátoru new se lze prakticky vždy vyhnout. Je-li z nějakého důvodu žádoucí použít explicitní ukazatel, nahradí jej make_shared. Jedinou výjimkou, která mě napadá, je použití nativní třídy v řizené třídě v C++/CLI. Tam překladač vyžaduje raw pointer. K uvolnění paměti se na první pohled nabízí metoda !Trida (finalizér). Správné je ovšem použití řízeného destruktoru ~Trida.
V zasade bych rekl, ze leaky hrozi vsude. a) V jazykach s garbage collectorem typu java, b) v jazykach s rucni alokaci a dealokaci i c) v jazycich, kde je to nejak dobastlene dodatecne jako v C++ a kde neni nejake jednotne vseobecne pouzivane reseni.
V zasade bych rekl, ze nejlip se mi to pise s gc jako v jave, ale celkem priznavam, ze dani za to je, ze kdyz nastane problem, tak clovek nemusi mit reseni ve svych rukach. Nejhur s mi to pise v c), ale to muze byt dost subjektivni.
Opravdu pekne, nicmene to ma jeden maly, ale podstatny hacek... ano, v C++ clovek muze psat kvalitne, bezpecne, atd... jenze.. JENZE, a to je jadro problemu: nemusi. Ano, N_E_M_U_S_I - takze staci jeden clovek v tymu ktery to nedodrzuje a memory leak je na svete.
Cely clanek se da v podstate napsat o JAKEMKOLIV jazyce, vcetne assembleru - protoze v JAKEMKOLIV jazyce se da pri striktnim dodrzovani pravidel (pouzivanim vhodnych knihoven, atd.) dosahnout kvalitniho kodu.
Podstatny rozdil je ale v tom ze ten kvalitni kod NENI zasluha jazyka, ale cloveka, ktery velmi disciplinovane pouziva "spravne" postupy.
Shrnuto: veskera zminovana ochrana pred memory-leaky a ztizene psani kodu, kde clovek musi vice premyslet je dano "OSVICENYM PROGRAMATOREM", nikoliv pouzitim C++ a tedy se da vztahnout na libovolny jazyk.
@4 Ne tak úplně, v C++ stačí zakázat používání new/delete (což by měla být samozřejmost). Co píšete se týká třeba ObjC (bez ARC), kde jsou pravidla a konvence, které je nutné dodržovat. V C++ člověk bez new vyrobí memory leak jen referenčním cyklem (kromě leaků, které lze vyrobit i s GC, např. ponechání instance v globální proměnné apod.).
@2 Však move sématika je taková statická analýza, akorát se analyzuje pouze kontrakt, není potřeba provádět hloubkovou analýzu použití, díky čemuž to funguje i přes hranice sdílených knihoven, obousměrně a do neomezené hloubky. To je totiž to, co se mi na vývoji C++ líbí: místo aby byla rovnou přidána zajímavá vlastnost, přemýšlí se nad tím, jestli to samé nelze udělat i nějak jinak, lépe a jednodušeji :)
@5 Ale to je prave to o cem mluvim: "v C++ stačí zakázat používání new/delete" je to, na cem to cele stoji a pada - a realita je bohuzel takova ze u vetsiny projektu to "zakazat" jde jen velmi tezko (resp. jde to casto pouze z pozice sefprogramatora nebo podobne).
Tedy dokud v C++ bude PRINCIPIALNE MOZNE zapsat new a delete (nehlede na to ze se najdou vykukove kteri i v C++ pouzivaji malloc/free - zazil jsem to), tak C++ bohuzel JE jazyk nachylny k memory leakum.
Proste v realnych podminkach JSOU programy psane v C++ nachylne na memory leaky. To ze je to zpusobene nedbalosti, hlouposti nebo lajdactvim je vedlejsi.
A oznacovat ty co to tvrdi za "veleosly" se mi vubec nelibi.
V C++ sice nedelam, ale prijde mi trochu divne oznacovat lidi za veleosly, kdyz jim o neco nize pak zakazujete new/delete. A pokud vim, shared_ptr tam take neni od zacatku.
Takze mozna, dnes to tak plati, a lze v (modernim) C++ psat bezpecne. Ale oznacovat lidi za osly jen proto, ze nesleduji posledni trendy? Kdyz pravem kritizovali neco, co bylo spatne navrzene? Trochu odvazne, rekl bych.
@10 Konečně zajímavá diskuse. Díky.
RIAA je v C++ odnepaměti. Zmiňovaný Stroustrupův příklad je někdy z 90. let. Můžeme to nazývat moderním C++, ale těžko posledním trendem. Kdyby oni kritici kritizovali výkon (bez move sémantiky, jež je vskutku "posledním trendem", by to bylo právem), nic bych nenamítal. Ovšem papouškovat bezmyšlenkovitě 20 let starou kravinu, to opravdu nesvědčí o přílišné inteligenci. Takoví by se měli aspoň zamyslet, a snad je vysvětlení v tomto článku snadné k pochopení. Na druhou stranu tito "kritici" beztak nevědí, jak funguje GC v Javě nebo C#, takže nemůžou srovnávat.
> Na diskusních fórech se stále dokola opakuje tvrzení, že v C++ hrozí úniky paměti, že je na ně náchylné
> V C++ člověk bez new vyrobí memory leak jen referenčním cyklem (kromě leaků, které lze vyrobit i s GC, např. ponechání instance v globální proměnné apod.).
Tak hrozí úniky nebo nehrozí?
> Opět lze namítnout, že existují deterministické GC, jenže ty se používají jen ve speciálních případech (typicky real-time systémech, i když tam se většinou GC nepoužívá vůbec), neboť mají spoustu jiných nevýhod.
Na druhou stranu GC může rozložit dealokaci mnoha objektů v čase.
Dalsi problem pouzivania new/delete je heap corruption, ked je delete volane 2 krat pre ten isty pointer. Je to velmi tazke analyzovat, ked to spadne u zakaznika. Aj ked mate crashdump, je vam to nanic, lebo program spadne na uplne inom mieste. Tu pomozu len programy ako Application Verifyier alebo BoundsChecker. A na nepoucitelnych code review, nech si pripadaju ako menejcenni :-)
RAII primárně není a nikdy nebyl o uvolňování paměti.
RAII je o pouhém zavolání destruktoru, jakmile skončí obor platnost proměnné, která nese objekt.
Stačí špatně naimplementovat destruktor a memory leaky nebo resource leaky budou hvízdat jak kulky na frontě.
Samozřejmě, že s pomocí RAII a vhodných tříd lze memory leakům do značné míry zabránit. Na rozdíl od GC se stará nejenom o uklizení paměti, ale obecně o uklizení všech použitých zdrojů, které objekty používají.
Nicméně musí být objekty dobře implementovány.
RAII negantuje uklizení paměti, RAII garantuje pouze to, že budou korektně zavolány destruktory všech objektů, kterým končí obor platnosti. Nic víc.
To nic nemění na tom, že při vhodném stylu se v C++ programátor o uvolňování paměti starat nemusí.
„Správný destruktor je žádný destruktor.“ – zřejmě názor nějakého přehnaného aktivisty.
„O nepaměťových zdrojích článek není, ty je nutné ošetřit explicitně s GC i bez.“
V C++ se ošetřuje obojí stejně – tedy často implicitně.
Výhoda RAII konceptu je deterministicky určené místo a čas úklidu. Na rozdíl od GC (s výjimkou reference counter).
Proc proboha zakazovat new/delete? Pochopim tedy snahu vyhnout se explicitnimu volani delete ale proc new? Jakekoliv pokusy nahradit new vedou k neprehlednemu kodu. To uz je lepsi new pretizit tak jak jsem naznacoval ve svem blogu a pouzivat alokatory, ktere nesou globalni zodpovednost za pridelenou pamet.
Největší problém v zakázání new/delete a používání move sémantiky vidím v tom, že se ochuzuji o OOP prvky C++. Hodně se mi stěžuje použití dědičnosti, programování proti rozhraní a polymorfismu.
K tomu "oslování" bych chtěl slyšet nějaký argument. Obecně z různých statistik a výzkumů vyplývá, že v praxi jsou memory leaky velký problém. Taky to je ten hlavní důvod proč je GC mezi tvůrci jazyků tak populární.
To už mi příjde smysluplnější zakazovat hvězdičky... rozumějte, používání přímých pointerů a vynucování používat chytré ukazatele. Opět to nejde vždycky, třeba při přetypování (třeba dynamic_cast) pointeru z chytrého ukazatele není dobré přetypovaný pointer ukládat zase do chytrého ukazatele. Takže se to nedá vynutit všude. Ale minimálně mezi memberemy třídy bych hvězdičku nerad viděl
Používám new i delete i další C-like funkce v C++ a hodlám v tom i nadále pokračovat. Pokud si někdo neumí otestovat aplikaci na úniky paměti, ať dělá v jazycích typu Java a necpe se mezi profi-programátory, kterým záleží jak na rychlosti, tak na efektivitě programování (díky OOP).
[33] "Používám new i delete i další C-like funkce v C++ a hodlám v tom i nadále pokračovat"
No nevím, sice jsem ze světa C/C++ už hezkých pár let pryč, ale jestli se ve vesmíru něco hodně nepokazilo, new a delete nejsou a nikdy nebyly "C-like funkce". Ostatně, ony to nejsou funkce ani v C++.
V prve rade velmi pekny blog, bookmark!
Ad GC, vs ne-gc, vs skopove (pun intended) alokace/uvolneni.
Myslim ze idealni je v dnesni dobe (i pro systems programming) opravdu GC, protoze zpravidla zapada do stylu "nevynalezat znova kolo, i za cenu toho ze holt nebude pornokino".
Osobne jsem si zvykl "high" konstrukce ( http://en.wikipedia.org/wiki/Resource_Acquisition_Is_Initialization#Ad-hoc_mechanisms ) a boehmgc i v cistem C, da se s tim jit dost nizko pokud hw neni vylozene spartansky - clovek ma vicemene prehled jak to funguje, takze neprichazi o deterministicke chovani (jako by to snad bylo az na minimum pripadu vubec potreba :).
Nevynucene GC co do produktivity v low jazycich na moji strane jednoznacne vitezi.
O rezii se vubec da rozsahle polemizovat - je lepsi nejaky slusny generacni mark & sweep, nebo refcounting pri predavani reference kazde shared blbinky, jakozto obcas v C++ templaty "vynucena" forma gc? Zalezi jak je program koncipovan.
Je-li to spis C-like monolit, tak refcounting je asi jasna volba (gamesky, linux kernel), s tim ze pooling/gc implementujem kde to smysl ma (linux kernel ma samozrejme GC v *mnoha* subsystemech).
Pokud to je pouze "komplexni bussiness logika", aka interakce mezi bazilionem objektu ktere se vsude mozne predavaj, refcounting spis generuje prusery - napriklad v Pythonu je refcounting udajne proto aby byl gc "deterministicky". Ok, napisu, odladim v techto podminkach a je fajn ze se mi spojeni do db zavre presne ve chvili kdy se ztrati posledni reference, teda az do ty chvile nez se nekde vytvori refcount cykl mrtvych objektu a musi se cekat na M&S. Oops.
Python to takto dela proto ze lidi proste maji tendence pouzivat M&S na uvolnovani ne-pameti, nedejboze volani destruktoru....
Nech je proklet ten co pouziva GC na uvolnovani cehokoliv jineho nez pameti, nebo se spoleha na deterministicke destruktory. Bohuzel to plati i pro vetsinu "high" veci jako Java, useknout ruce :( Vetsinou to funguje bez problemu, nez se objevi nejaka takovato zprasena ohyzdnost, nicmene GC je v tom nevinne, pouze jeho mizerne uziti v mistech kde vubec nema co delat.
>> Takoví existují?
Java, to je celej narod kaciru :) Zrovna dneska jsem se spalil s http://bugs.sun.com/view_bug.do?bug_id=4724038
Autor se zabývá vývojem kompilátorů a knihoven pro objektově-orientované programovací jazyky.
Přečteno 36 200×
Přečteno 25 361×
Přečteno 23 795×
Přečteno 20 177×
Přečteno 17 874×