To je hlod
|
27.8.2014 11:19
Jan Němec
|
Ježíši, to je zase C++ hlod, s Váma bych nechtěl dělat v jednom týmu :-) Jinak bych doporučoval nenechávat se unést fanouškovstvím pro C++ (nebo některé jeho vlastnosti) a aspoň trochu se snažit o objektivitu. V zásadě bych řekl, že třeba
"C++ dotáhl uklízení lokálních proměnných na plný automatický úklid a to pro jakékoli proměnné jakéhokoli typu." (Ne. V C++ si můžu ručně naalokovat pointr, klidně přes malloc, ten se sám od sebe neuvolní.)
"RAII je voláno okamžitě bez prodlení a přesně v čase, kdy zdroj přestává být potřeba." (Ne. Destruktor se volá v době konce platnosti proměnné. Čert ví, kdy opravdu přestává být potřeba, lokální proměnná klidně v půlce bloku.)
"'Má nebo nemá C++ garbage collector?' Jak vidíte, odpovědět jde opravdu těžko." (Nemá. Má automatické volání destruktoru, má auto pointer, můžu si sám napsat nějaký jiný smart pointer... Ale garbage collector C++ nemá.)
"pokud se v C++ někdo musí starat ručně o uvolňování paměti, tak neprogramuje v C++" (C++ je všechno to, co je v normě C++, tj. i skoro celé skoro nezměněné C. Když programuju aspoň trochu normálně v C, tak programuju zároveň i v C++.)
jsou už zcela jednoznačně nepravdy. |
|
|
Re: To je hlod
|
5.9.2014 15:18
Miloslav Ponkrác
|
1) "C++ dotáhl uklízení lokálních proměnných na plný automatický úklid a to pro jakékoli proměnné jakéhokoli typu." (Ne. V C++ si můžu ručně naalokovat pointr, klidně přes malloc, ten se sám od sebe neuvolní.)
Podstatný je ten termín „lokální proměnné“.
Jak jsem psal v článku, budete-li chtít pointery řešit ručně, C++ do toho zasahovat nebude. Budete-li je chtít uvolňovat automaticky, pak to samozřejmě lze, a to i u pointerů alokovaných pomocí malloc. Stačí ho šoupnout do chytrého pointeru a dále na to zapomenout. Nic se nezmění, syntaxe nakládání s pointerem je stejná, jako v případě ručného ošetřování. Jen se automaticky uvolní, až nebude třeba.
----
2) "RAII je voláno okamžitě bez prodlení a přesně v čase, kdy zdroj přestává být potřeba." (Ne. Destruktor se volá v době konce platnosti proměnné. Čert ví, kdy opravdu přestává být potřeba, lokální proměnná klidně v půlce bloku.)
Je takový nepsaný zvyk, dobrý zvyk ze starých časů, že obory platnosti proměnných (scope) korespondují s dobou potřeby daných proměnných. Je to základní poučka, která se jako první učí v sw inženýrství.
Pro nouzový, velmi nepravdědpobný případ, kdy zdroj nutně potřebujete uvolnit dříve, existují metody jako je release() či close(). Ovšem takový případ také něco říká o stylu daného kódera.
----
3) "'Má nebo nemá C++ garbage collector?' Jak vidíte, odpovědět jde opravdu těžko." (Nemá. Má automatické volání destruktoru, má auto pointer, můžu si sám napsat nějaký jiný smart pointer... Ale garbage collector C++ nemá.)
A jakápak je definice slova „garbage collector“? Pro jistotu ji opíši z wikipedie: „In computer science, <b>garbage collection (GC) is a form of automatic memory management.</b>“
Jakmile nepotřebujete ručně ve zdrojovém kódu uvolňovat paměť, tak se nejedná, po Klausovsku zůdrazňuji opakovaně, nejedná, o manuální uvolňování paměti, ale o automatické uvolňování paměti, tedy garbage collector.
----
4) "pokud se v C++ někdo musí starat ručně o uvolňování paměti, tak neprogramuje v C++" (C++ je všechno to, co je v normě C++, tj. i skoro celé skoro nezměněné C. Když programuju aspoň trochu normálně v C, tak programuju zároveň i v C++.)
Myslím, že je jasné, že jsem mluvil o stylu programování.
Toto není ani vědecká, ani právnická práce, je to úvaha a článek psaný běžným lidským jazykem.
Staré profláklé heslo „jsou programátoři, kteří programují ve Fortranu v každém programovacím jazyce“ snad napoví, o čem je řeč.
Existují programátoři, u kterých poznáte, že znají jeden programovací jazyk, a to i když programují třeba ve 20 jazycích. Jsou programátoři, kteří „programují v Javě v každém programovacím jazykce“, stejně tak jako jsou programátoři, kterří „programují v C v jakémkoli programovacím jazyce“, atd.
|
|
|
Re: To je hlod
|
8.9.2014 11:28
Jan Němec
|
"Je takový nepsaný zvyk, dobrý zvyk ze starých časů, že obory platnosti proměnných (scope) korespondují s dobou potřeby daných proměnných. Je to základní poučka, která se jako první učí v sw inženýrství."
void [trida::]funkce_nebo_metoda()
{
typ jednoducha_promenna_nic_velkeho;
// neco delam s promennou
// posledni pouziti promenne
nejaky_kod_treba_i_jen_jeden_radek_bez_promenne();
}
Vám tohle přijde jako prasárna? To jako fakt dáváte to použití jednoducha_promenna_nic_velkeho do bloku nebo to rezepíšete na 2 funkce? (Pokud ne, tak rozhodně není pravda, že "RAII je voláno okamžitě bez prodlení a přesně v čase, kdy zdroj přestává být potřeba.") |
|
|
Re: To je hlod
|
8.9.2014 16:26
Miloslav Ponkrác
|
Myslím, že slovíčkaříme. Jsem rád, že jsem Vám vysvětlil, a neprotestujete, že bloky by měly plus mínus korespondovat u dobrého stylu s platností lokálních proměnných. Souhlasím s tím, že RAII neruší proměnnou přesně v době, kdy zdroj přestává být potřeba, to opravdu neumí, ale v okamžiku konce platnosti proměnné. Na druhé straně právě proto jsem psal o programátorském stylu. Protože kompilátor není věštec, a tak dobu, kdy je proměnná potřeba odhaduje z jejího oborou platnosti + z jejího použití.
Závěr: V tom máte pravdu. Já jsem v článku předpokládal, že programujeme dobrým stylem. Máte pravdu, že RAII neruší v době, kdy není proměnná potřeba. To kompilátor opravdu nemá v mnoha případech jak poznat. Ovšem programátor má učinit dobu platnosti proměnné a její potřebnost do souladu. Tam, kde na tom záleží, a proměnná drží významné zdroje se prostě udělá separátní blok, nebo se zdroj odtraní ručně.
********
Pro kompilátor je jakákoli proměnná potřeba až do konce jejího oboru platnosti. Jak jistě víte, kompilátor nemusí provádět kód v té sekvenci příkazů, kterou jste napsal, a je oprávněn přehazovat pořadí příkazů, pokud to nezmění výsledné chování.
Teoreticky kompilátor ruší lokální proměnné na konci bloku, ve skutečnosti je může zrušit i dříve, přesně po čase posledního použití, pokud optimalizátor vydedukuje, že se tím docílí stejného chování, jako kdyby se zrušila na konci platnosti. A C++ kompilátor většinu proměnných zruší mnohem dříve, než je obor platnosti – doporučuji disassemblovat nějaký výsledný kód dobře optimalizující kompilátoru. Budete se hodně divit. |
|
|
Re: To je hlod
|
22.9.2014 08:35
Aleš Hakl
|
Kompilator generuje kod, jehoz pozorovatelne chovani je stejne, jako kdyby se v nejakem poradi provedli casti oddelene sequence-pointy.
Kompilator umi pomerne snadno dokazat, ze promenna uz neni dale v bloku potreba, co ovsem dokazat rozumne neumi je, ze presun volani netrivialniho destruktoru z konce bloku nekam jinam chovani nezmeni (to stejne plati i o miste volani konstruktoru). Takovy prakticky priklad toho, kdy zmeni je promenna reprezentujici drzeny zamek (class F{ F() {pthread_mutex_lock(foo);}; ~F(){pthread_mutex_unlock(foo);} }), nebo obecne cokoli, co globalni stav programu ovlivnuje svoji prostou existenci, tj. veci ktere se uvadeji jako typicke priklady toho spravneho RAII.
Je to zase dalsi z mist, kde C++ garantuje/umoznuje neco, co vicemene zbytecne zamezuje spouste optimalizacnich prilezitosti. |
|
|
Re: To je hlod
|
25.9.2014 03:11
Miloslav Ponkrác
|
Programovací jazyk umí využít k optimalizaci jen to, co se dá vydedukovat ze zdrojového kódu. Tedy z informace, kterou dodal programátor.
Programovací jazyk něco garantuje, a je na programátorovi, aby si na základě garancí udělal co potřebuje. Garance dokonalého uklizení lokálních proměnných a garance, že program bude dělat to, co je napsáno ve zdrojovém kódu – jsou docela nezbytností.
----
To hlavní, co brání optimalizaci v C/C++ je pointerová artimetika a nešťastná syntaxe pointerové aritmetiky. Program musí předpokládat, že pointery na cokoli se mohou objevit kdekoli v programu a nemůže téměř nic odvozovat. Kompilátor předá funkci pointer na strukturu, a musí předpokládat, že odteď může být struktura čtena i zapisována kdekoli v programu až do konce programu (samozřejmě s omezením na scope). A že se kdekoli mohou objevit pointery i na podčásti struktury. S tímto se hodně blbě optimalizuje.
C/C++ není schopno dost dobře si udělat systém vzájemných vztahů mezi proměnnými, funkcemi a thready uvnitř programu, protože pointerová aritmetika, stejně jako omezené informace mu to ani nedovolí. |
|
|
Re: To je hlod
|
8.9.2014 16:29
Miloslav Ponkrác
|
Možná by se to dalo zkrátit: Proměnná je potřeba, dokud je v oboru platnosti. Tečka. |
|
|
Re: To je hlod
|
29.9.2014 15:35
Jan Němec
|
To je skoro takové tvrzení, jako "Je třeba tolik rohlíků, kolik naplánuje plánovací komise. Tečka." nebo v kapitalistické variantě "Je třeba tolik rohlíků, kolik zařídí neviditelná ruka trhu. Tečka." Nebo možná ještě názorněji "Vypůjčené nářadí potřebuji vždy přesně tak dlouho, dokud ho nevrátím." Ve skutečnosti ani jedno z těch tvrzení není pravda. Kdyby C++ uměl uvolňovat proměnné přesně v místě, kdy už nejsou potřeba, tak by musel překladač řešit problém zastavení (Turingova stroje). Nic proti uvolňování na konci platnosti proměnné, je to naprosto normální věc. Jen bych to nenazýval tím reklamním sloganem, to je vše. |
|
|
Re: To je hlod
|
1.10.2014 00:26
Miloslav Ponkrác
|
Jak víte, že C++ kompilátor problém, kdy přesně nejsou proměnné potřeba neřeší? Samozřejmě že řeší, byť to závisí na kompilátoru, a většinou v tom není úspěšný ve složitějších případech, ale v jednodušších je to dnes běžné chování optimalizujících kompilátorů.
Jinak samozřejmě tato debata je opravdu hloupá. Kompilátor má údaje od programátora ze zdrojového kódu. Plus vše, co lze ze zdrojového kódu (a vlastností cílového prostředí platformy překladu) vydedukovat. Není to mnoho, ale není to vůbec málo.
Když potřebujete hodnotu typu integer, tak mu to napíšete, a nehádáte se v žádné diskusi, že má kompilátor číst myšlenky, a že to nepozná. Nedokáži si představit, že bychom diskutovali už kolikátý příspěvek o tom, že jste dal jiný typ než chcete a viníte kompilátor, že to nepoznal.
Když potřebujete někde suše ukončit s jistotou platnost proměnné, ukončíte mu tam blok. Je to zcela stejné. Přesto diskutujeme už x příspěvků, kde nejdříve jsem byl nucen napsal, že „proměnná je potřeba“ znamená v programátorském jazyce „je v oboru platnosti“ a programátor by měl tento záměr co nejvíce naznačovat takto.
Už po x-té se tu hádáme, že kompilátor C++ není schopen vydedukovat, že proměnnou je možné zrušit dříve, než před koncem oboru platnosti. Samozřejmě že v řadě případů je toho schopen, ale závisí to na kompilátoru a kvalitě jeho optimalizátoru. Bez této dedukce by z kompilátorů lezl velmi pomalý a nehezký kód. Ovšem jistota dedukce zde není, protože chcete-li jistotu, je jasně řečeno, že obor platnosti je tvrdá informace kompilátoru o potřebnosti proměnné. A vytvoření dodatečného bloku je zdarma.
Buď budete mluvit s kompilátorem způsobem, kterému rozumí, a nebo to nebude fungovat. |
|
|
Díky za článek
|
4.9.2014 14:14
František Kučera
|
Musím přidat i jeden pozitivní komentář. Sice jsem skalní Javista, ale rád se naučím i něco jiného a podívám se, jak to řeší jinde.
Ten RAII princip je zajímavý. A v Javě na ho vlastně máme taky: try + AutoCloseable – sice to přidává složené závorky a další úroveň zanoření, ale zdroj se zavře ve chvíli, kdy končí platnost proměnné (zavolá se close(), což je důležitější – i když paměť požere GC až o něco později).
Ale uvítal bych trochu víc rozvést využití tohoto principu v případě, kdy se objekty předávají někam dál a nejsou jen v lokálních proměnných (tam je snadno pochopitelné), to jsem z toho příkladu moc nepochopil resp. přijde mi, že bude spousta případů, kdy to nebude fungovat a ty zdroje se samy nezavřou a neuvolní. |
|
|
Re: Díky za článek
|
5.9.2014 15:34
Miloslav Ponkrác
|
Přiznám se, že v Javě mé znalosti nejsou zcela úplné, ale po Vašem příspěvku mě Java v tomto aspektu začala zajímat. Takže jste mě motivoval ke studiu.
Možná jsem měl článek napsat trochu konkrétněji, nebo napsat druhý díl s příklady. Celý princip automatického uklízení v C++ vychází ze dvou principů:
1) První princip je RAII. Pokud pointer zůstává uvnitř funkce/metody, či jednoho bloku uvnitř metody, pak stačí RAII. Ve standardní knihovně existuje několik tříd zvaných chytré pointery, do kterých pointer uložíte, a dojde k automatického uvolnění na konci oboru platnosti. S pointerem můžete zacházet dále dle libosti.
2) Druhý princip je syntaktický cukr udělaný pomocí „chytrého pointeru“. C++ má přetěžování operátorů a tak může vytvořit třídu („chytrý pointer“), který bude reagovat prakticky syntakticky stejně v C++ jako pravý pointer.
Pokud předáváte pointery mimo funkce/metody, třeba jako návratové hodnoty, a nebo pointery cestují a rozkopírovávají se na různá místa, pak je třeba použít jak RAII, tak ten syntaktický cukr chytrého pointeru. Ve skutečnosti nevracíte pointer, ale třídu „chytrý pointer“.
Programátor nepozná rozdíl (kromě typu), protože s chytrým pointerem se zachází stejně jako s pointerem. Ale protože je „chytrý pointer“ třída, a dokonce si ji můžete napsat i sám, pokud byste nechtěl používat standardní knihovnu, tak udělá cokoli budete chtít. Jeden z chytrých pointerů – třída std::shared_ptr má uvnitř čítač odkazů, a tedy počítá, na kolika místech se pointer vyskytuje. A když čítač dosáhne nuly, tak zdroj uvolní.
---
Podstata tedy v podstatě je v tom, že pokud se dostane pointer či jiný zdroj mimo lokální kontextu, ve skutečnosti se podstrčí namísto pointeru či zdroje obálka z chytrého pointeru.
|
|
|
|
|
|
KOMENTARZE
|
Tylko zarejestrowani użytkownicy mogą dopisywać komentarze.
|
|
Szukanie oprogramowania
|