Já už jsem se k tomu vyjadřoval jinde. Těch 70% problémů s pamětí je dáno tím, že 70% vývojářů Chrome neumí C++. Nebo se učili C++ před rokem 2011. Nebo dodnes nikdo nepřepsal velmi staré kusy kódu v Chrome (Webkit, blink). I to, že jsem s tím kódem měl tu čest někdy v roce 2014 pracovat, protože jsem musel mít jeho fork.
od C++ 11 máme std::unique_ptr a std::make_unique. Sice to ještě není ekvivaletní borrow checkeru, ale je to na dobré cestě. Pokud totiž si v C++ neděláte vlastní správu paměti, na problémy memory leaků nemáte prakticky šanci narazit - vyjma speciálních situacích, které nepořeší například referenční čítače na shared_ptr u kruhových referencí - kde to chce buď lépe promyslet strukturu aby nevznikaly cykly, nebo použít nějaký GC.
Co mi v zásadě v normě chybí je možnost označit && referenci jako force-move, tedy atribut který oznámí kompilátoru, že proměnná, kterou narvu do takové reference bude po návratu neplatná, protože se tady zaručeně stěhuje vlastnictví (klasický std::move sice označuje místo přesunu vlastnictví, ale když ho volaná strana nepřesune, pak je původní proměnná stále platná)
Dál mi samozřejmě chybí zamykatelné reference - které si člověk ale může napsat sám, jen to prostě není v normě. Takové to, že když si přistoupit k objektu, musím ho zamknout buď shared lockem (dostanu const ref) nebo exclusive lockem (dostanu mutable ref). Tohle řeší přístup pro vícevláknový přístup, který se podle mne musí dělat runtime (i ten RUST tam někde bude mít zámky na úrovni RT).
Tohle je ten starý paeudoargument, že vývojáři Googlu "neumí C++" (zatímco každý Pepa na rootu je samozřejmě expert, jenom škoda, že se svět neobrátil na něj, a přitom dokázal napsat hello world...) Google zveřejnil i jiná, stejně známá data, že podobné procento bugů je méně než rok starých. Už nevím, kde to bylo, vygooglujte si to. Jak sami autoři zdůrazňují, neznamená to, že by nový kód byl horší, než ten starý. Znamená to, že jak se starší bugy řeší, neustále přibývají nové. Neboli i s celým std::move a unique_ptr a spol. C++ nedokáže zaručit správnost kódu. Mimo jiné proto, že tyhle věci jsou v podstatě kosmetické, doopravdy nefungují.
Sdílení referencí mezi vlákny se v Růstu řeší dost jiným způsobem.
Vážně? Tak co třeba tohle:
std::vector<int> v;
v.push_back(666);
auto u = std::move(v);
std::cout << v[0] << std::endl;
Jakýkoli C++ compiler to vezme, a jak jsem si právě ověřil s g++ 11.2.1, při běhu to udělá jak jinak než segfault.
Takže vaši poznámku beru tak, že toho, že to nefunguje, jste si opravdu nevšiml :)
to je správné chování.
protože v je prázdné pole.
Špatně je tu, že operator[] nemá kontrolu rozsahů. A to z důvodu efektivity.
A kdybyste četl celý můj příspěvek, přesně o tom jsem psal. Chtělo by to do C++ přidat mechanismu, kdy explicitně prohlásím po použití std::move proměnnou jako více neexistující aby překladač označil použití v za nesprávné.
Na druhou stranu, je to forma syntaxtického cukru, nic co by se nedalo udělat.
Špatně není operátor [], špatně je, že po move je proměnná v stále přístupná a lze s ní operovat. V rustu by se stejný kód nezkompiloval (natož aby prostě padal).
A "vo tom to je". unique_ptr v C++ vůbec nemusí být jediný, resp. neexistuje způsob, jak to zajistit. Jakýkoli objekt se může zničit, když na něj ještě jsou reference (&), které pak vyvolají segfault. U vectoru je nutné ručně vybírat mezi morem a cholerou ([] nebo .at()), protože překladač nedokáže sám odvodit, kdy je bounds check nutný a kdy ne. Sémantika move constructoru je naprosto neuvěřitelná. Tzv. koncepty z C++20 nezaručují prostě vůbec nic (není nejmenší problém v šabloně volat metodu, která v konceptu není deklarovaná). Atakdále. Zkrátka tyhle "bezpečné" featury z C++ post-11 jsou jen o málo lepši, než si tam dát prostě komentář
// od teďka na v nesahat
Z hlediska statické prokazatelnosti je to zhruba na té samé úrovni.
No v Rustu máte například explicitní move - teda sorry, nemáte, záleží na atributech objektu, které se přesouvají, jiné se kopírují. Z kódu lokálně jasně není vidět, co nastane. Takže jsem radši za std::move.
To že po std::move se proměnná neoznačí jako nepřístupnou jsem psal.
Překladač nemůže odvodit zda je nebo není bound checking, pokud nemá k dispozici celý kód například pokud voláte knihovnu. Pokud ručně kontrolujete rozsahy a kód je přístupný a následně se během optimalizace zjistí, že se podmínky vždy vyhodnotí false, tak pak tu kontrolu rozsahu se z toho odebere a není tam.
Proč operator[] nemá kontrolu rozsahů netuším, je to nějaké designové rozhodnutí. Protože stejně jako na tom chrome, i na tom C++ pracuje mnoho tvůrců a každý má jiný pohled na svět. Takových divných designerských rozhodnutí najdete i v Rustu, například rozhodutí nepoužívat výjimky a následně zavést operátor? Ale určitě se do roztrhání těla budete být za svojí víru. Prostě to berte jako fakt. Lze si napsat vlastní verktor, který tam tu kontrolu má (nebo ho podědit) a jste safe
Koncepty v C++20 - bod pro vás, že jste to studoval. Opět si myslím, že je to designerské rozhodnutí, volba mezi komplikovaným překladem a deklarací ve které když něco deklaruju a pak to nedodržím, tak je to samozřejmě můj problém.
O žádnou víru se nejedná, kdysi jsem se taky bil do roztrhání těla za "svůj" jazyk, dneska si troufám říct, že jsem zmoudřel. Mezi výjimkami a operátorem ? v Rustu je zásadní rozdíl a osobně v tom vidím výhody i nevýhody, nepatřím k těm, kteří výjimky apriori zatracují (i když zrovna ty v C++...)
Problém je v tom, že když něco nedodržíte, tak to není váš problém. Jakmile se dostanete do řádu desítek MLoC tak zaprvé je absolutně nemožné si to pamatovat, především ale to pak není váš problém, ale problém toho, kdo se bude snažit o několik let později implementovat nějakou úplně jinou featuru a od vašeho modulu bude mít k dispozici v nejlepším případě dokumentaci API. Ten pak bude mlátit hlavou o zeď, proč něco segfaultuje. Jistěže si můžete implementovat vlastní vector (ale on bude mít zase svůj vlastní vector, který bude stejně zabugovaný, jako ten váš, jenom jinak). Nebo si klidně můžete implementovat vlastní programovací jazyk. Na problému se tím nic nemění.
A víte jak se pozná dobrý a špatný programátor
Dobrý programátor má pravidla, jak má vypada dobře napsaný program. Špatný programátor spoléhá na překladač, že mu ukáže chyby.
CO je nějaký segfault proti tomu, že mne robot zlikvidoval na burze pozici, protože jsem v jeho kódu sčítal nějakou proměnnou dvakrát a ne jednou. Sakra, že mne na to překladač neupozornil....?
Ano, celé poselství je v tom, že když si něco deklaruju a pak to nedodržuju, mohu se těšit na budoucí problémy. Pokud to ale dodržuju, problémům se vyhnu. Je to tak těžky to dodržovat? Ušetříte práci nejen sobě, ale budoucím generacím.
V rustu nezavedli výjimky
A když zjistili, že se strašně blbě pracuje s chybama, zavedli operátor ?. který je syntaktickou zkratkou na to vyhodit chybu z fukce pokud volaná funkce vyhodila chybu.
Což je zhruba úplně totéž, co dělají výjimky.
Takže už je na dobré cestě.
jen tak mimochodem std::make_unique vlastně není potřeba. Stačí napsat do konstruktoru std::unique_ptr obyčejné new. Ale jo, není to hezké, právě třeba pro automatické code review. Máš v programu new? No buď máš k tomu nějaký fakt dobrý důvod, nebo tam nemá co dělat...
27. 9. 2021, 10:20 editováno autorem komentáře
Náhodou má do určité míry pravdu, že ? v Rustu v podstatě emuluje stack unwinding. Akorát návratový typ je explicitně Result (což je IMHO lepší, nekomplikuje to syntax jako try-catch). Go mělo podobnou konstrukci, ale návrh neprošel schvalováním (místo ? na konci psali try na začátku, jinak to bylo na chlup stejné).
Jenže v C++ lze výjimku odchytávat kdekoli, nejen na hranici funkce, kde byla vyhozena. Kromě toho (pokud se něco nezměnilo, co mi uniklo), nemá C++ checked exceptions. Výjimka v C++ je neřízená střela, kterou někdo někde vypálí a když vše dobře dopadne, někdo další ji zachytí na tom správném místě.
Result v Rustu je normální výraz, který musí programátor v každém bodě zpracovat a to, že existují zkratky typu ? a unwrap(), na této situaci nic nemění. I to ? pořád zachovává nutnost deklarovat typ Resultu a při další propagaci s ním řádně zacházet.
Ano, odchytit výjimku "kdekoli" v rámci funkce lze zpracováním větve Err, ale nelze pomocí ? prostřelit ten Err skrze několik návratů funkce najednou. Ta zkratka je prostě IMO hodně nepřesná.
Jasně, unwrap() je fuj, ale je to explicitní a jasně definované fuj, které se snadno dohledá a kdykoli vyřeší.
Jedno to není ani náhodou. Výjimka je stack unwind, ? je normální návrat z funkce ovšem se speciální hodnotou. V praxi to má dost dalekosáhlé důsledky.
Výhoda výjimek:
Chyby se propagují vlastní cestou mimo normální control flow. Po návratu z funkce ? vyžaduje další podmíněný skok, což v extrémním případě může mít dopad na výkon. Naopak výjimky zase vyžadují náročnější správu zásobníku, co je horší záleží na okolnostech.
Výhoda Result/?:
Výsledek (ať už skutečný, nebo chyba) je hodnota, jako každá jiná, a jako s takovou se s ní dá pracovat. Například můžete paralelně spustit N operací, z nichž každá vrací Result přes ?, výsledky dávat do pole a pak vidět, které uspěly a které ne. Můžete vracet chyby a odchytávat je v jiném vlákně (výjimky napříč vlákny je přímo kapitola...). Samotné chyby jsou pod kontrolou typového systému a borrow checkeru.
Výhoda výjimek:
Ne všechny chyby se dají doopravdy napravit, zvlášť ne lokálně v kódu, Někdy je prostě nutné jenom konstatovat, že nastal průs...r, případně něco uzavřít nebo ukončit a hotovo, a to se děje někde na úrovni N+M v call stacku. S výjimkami je to podstatně snažší, navíc možná méně svádějí ke zneužití. U Result a ? je riziko, že někdo bude chtít psát "chytrý" kód, např. jsem už viděl, že někdo Result "chytře" propagoval směrem dolů jako argumenty volaných funkcí....
Výhoda Result/?:
Když nějaká rutina nebo metoda v nově verzi začne vracet nové chyby, které dřív nebyly, automaticky se to stane součástí její API a kompiler nedovolí je ignorovat.
27. 9. 2021, 11:36 editováno autorem komentáře
klokan:
Jste krásně popsal výjimky jak jsou implementované TEĎ. Ale pletete si implementaci s jazykovou konstrukcí.
Například kdybych psal C++ wrapper do Rustu, budu na základní úrovni výjmky odchytávat a vracet je jako chybový result. A obráceně, pokud bych psal C++ wrapper na Rust, budu chybové resulty vyhazovat jako výjimku.
Nevím jestli je v normě psáno explicitně, že zpracování výjimky má jít jinou cestou. Hmm, podle mě nemusí, ale to je jedno. Každopádně to vaše fuj stack unwind je úplně na stejné úrovní jako
if (chyba) return chyba
uprostřed funkce. I tady se musí zahodit vše, co funkce doposud vytvořila a uložila na zásobník.
Samozřejmě, když TOMU ČLOVĚK NEROZUMÍ, tak se do toho zamotá, protože výjimky často chodí přes operační systém. I když by nemusely, tak chodí. Nevím proč, možná pro lepší kontrolu, nebo možnost odchytávat výjimky i z kódu, který není C++ kompatibilní, nebo když vyhodím výjimku v C++ a neodchytne ji nikdo až nějaká ne C++ funkce, která původně C++ funkci volala. Asi nějaký takový důvod to má. Bez operačního systému by šlo implementovat výjimky stejně jako v Rust, akorát by to bylo transparentní a programátor by nic nepoznal.
"Tohle je ten starý paeudoargument, že vývojáři Googlu "neumí C++" (zatímco každý Pepa na rootu je samozřejmě expert, jenom škoda, že se svět neobrátil na něj, a přitom dokázal napsat hello world...) "
Hmm, on ten chrome staví hlavně na open source prohlížeči kde se podílelo mnoho dobrovolných vývojařů. A stačí si ten kód otevřít a zjistíte, kterou část psal který pepa. Protože k velké účtě open source komunitě, nemělo by se to přehánět. V okamžiku, kdy program je víc umělecké dílo než funkční celek a k tomu aby to pořádně fungovalo to musí někdo lepit k sobě kobercovkou, pak to podle toho vypadá.