Přidat otázku mezi oblíbenéZasílat nové odpovědi e-mailem Základné pravidlá pre písanie kódu v C++

Programujem uz nejaký ten mesiac aj niečo v C++ prešiel som naň z vyšších jazykov je to krásny jazyk

Problém je že mi dáva až príliš veľa slobody a ja potom trávim čas voľbou rôznych alternatív, hrajkám sa s tým čo potom vedie k predčasnej optimalizácii. Ono to vyberanie je super a na C++ sa mi páči že môžem vyberať ale toto sa robí pokiaľ už mám appku hotovú a optimalizujem kritické miesta.

Najlepšie je spraviť si prototyp zistiť či to funguje a podľa prototypu návrh a pri tomto návrhu už myslieť na základné optimalizácie.

A potom nakódiť appku a ďalej ju ešte zrýchliť a zoptimalizovať.

Aby som tento proces rozhodovania urýchlil urobil som si sadu pravidiel Chcem sa opýtať skúsenejšúch C++ kóderov či tá sada pravidiel má niečo do seba alebo je to celé nezmysel. Alebo ak s tou sadou nesúhlasíte napíšte či máte inú sadu, alebo ako by ste vylepšili tú moju

A) alokácia pamate

Najdôležitejšie sú prvé dve pravidlá:

1. tam kde sa vo vyšších jazykoch používajú hodnotové typy (čísla, malé stringy, malé kontainery) s krátkou platnosťou budem preferovať alokáciu na stacku
2. tam kde sa vo vyšších jazykoch používajú referenčné typy alebo veľa hodnôt za sebou (velké pole, velké kontainery, velké stringy) alokujem pamať na halde a použijem
a) shared_ptr ak chcem podobné chovanie ako pri referenciách vo vyšších jazykoch (viac pointerov môže ukazovať na rovnaké miesto v pamati)
b) unique_ptr za podmienky že nechcem aby viac premenných odkazovalo na rovnaké miesto v pamati unique_ptr - výhoda je v tom že sa automaticky uvolní pamať ak už nie
je potrebná (ak chcem pointer premiestniť inde použijem std::move)
c) kedy použiť weak_ptr?

Toto je základný kľúč. A potom už len optimalizácie:

3. ak potrebujem veľmi rýchlu pamať alebo objekty kopírovať hodnotou a som si istý že nepotrebujem využívať pokročilé možnosti OOP (prepisovať virtuálne metódy v predkovi) objekt môžem vytvoriť aj na stacku. V takom prípade sa bude kopírovať hodnotou. Práca s objektom vytvoreným na stacku je rýchlejšia zároveň je ale veľkosť stacku obmedzená preto ak tých objektov bude viac alebo budú zaberať veľa miesta je lepšie ich vytvoriť na halde.

4. ak pri objekte uloženom na stacku chcem podobné výhody ako má halda (nechcem nič kopírovať iba ukazovať na miesto) použijem &

B) bezpečnosť
1. preferovať immutablitu - všade kde sa dá používať const aby som mal istotu že neprepíšem niečo čo prepísať nechcem
2. ak viem že z objektu budem dediť tak pri verejných a chránených memberoch používať virtual / override v tom prípade ich ale treba inštanciovať na halde
3. na binárne dáta nepoužívať char* ale vector<char> keďže char* berie \0 ako ukončovací znak
4. referenčná transparentnosť - funkcie by nemali pristupovať k okolitému svetu funkcia dostáva argumenty a z nich vypočíta výsledok ktorý vráti referenciám sa vyhnúť pokiaľ je to možné (a ja nepotrebujem výkon) ak chcem vrátiť viac hodnôt použujem tuple)

- pri návrhu základnej kostry aplikácie treba klásť dôraz na bezpečnosť.
- ak sa ukáže ukáže že je nejaká časť aplikácie pomalá vykašlať sa na bezpečnosť, immutablitu a referenčnú transparentnosť a zoptimalizovať to tak aby to kritické
miesto bolo rýchlejšie a potom túto menej bezpečnú časť appky riadne potestovať
Odstraněn tag code. (karel)

Předmět Autor Datum
No, neco to do sebe ma, ale ja na to nahlizim trochu jinak - nejdriv to naprogramuju tak, abych se v…
gilhad 12.11.2019 02:59
gilhad
C++ je pomerne pekný jazyk, hlavne jeho posledné verzie. Nanešťastie je aj pomerne komplikovaný, hla… poslední
moose 12.11.2019 08:35
moose

No, neco to do sebe ma, ale ja na to nahlizim trochu jinak - nejdriv to naprogramuju tak, abych se v tom vyznal i po letech, bylo to prehledne, bezpecne a fungovalo to spravne.

Potom zkusim, jak rychle/velke/nenazrane to je a jestli je to vubec problem. Pokud ano, tak otestuju, kde to nejvic drhne a tam to zacnu zlepsovat. Pokud to zlepseni nestaci, vyprofiluju nove uzke hrdlo a optimalizuju tam ... dokud to neni dost dokonale, nebo dokud me to bavi. Je vyrazny rozdil usetrit par sekund v setupu, ktery probehne jen jednou a usetrit par milisekund ve smycce, kterou to probehne milionkrat (asi tak tisicinasobny :-D ).

Naopak pokud to nechodi spravne a bezpecne, tak je zrychlovani na nic (jako cely program v tu chvili), takze ma pro me ta spravnost a bezpecnost vyrazne vyssi vyznam, nez rychlost a velikost. (A to i na embeded mikroprocesorech, kde se bavime o desitkach kB na program a jednotkach kB na data - i tam velikost resim az kdyz musim a pokud je to vyrazne driv, nez to je funkci, tak je to znameni, ze na to jdu spatne, mam to psat a ladit po mensich castech a eventualne to i rozhodit mezi vic pocitacu, nebo pouzit zcela jiny pristup)

Co se haldy a stacku tyce, tak stack pouzivam pro promenne lokalni, haldu pro promenne sdilene z vic mist (ono se to beztak bere z jedne pameti, akorat z jine strany a de/alokace na halde je pomalejsi a muze delat problemy s fragmentaci pameti) - i kdyz ono to dost casto vychazi podobne jako ta tvoje pravidla, jen k tomu vede jina filosofie.

Ad 4. naprosto OK reseni, pokud se ti podari ZAJISTIT (z principu veci), ze se o ten objekt prestanou zajimat vsichni ostatni driv, nez skonci funkce, ktera ho na stacku vytvorila - v opacnem pripade ti ho proste bestialne uvolni a to misto budou pouzivat jine funkce (ci halda), psat tam ruzne jine veci a kazde pouziti takovehoto objektu jednak muze vracet/pouzivat spatne a principialne i nesmyslne hodnoty, jednak ti muze zbortit prakticky cokoli kdekoli, vcetne navratovych adres funkci - coz je oboji peklo na koleckach nejak odchytat a opravovat, protoze se to chova, jak se tomu zrovna zlibi (a na potvoru to muze fungovat zakerne spravne i pri ruznych testech - nez se to rozhodne fungovat jinak).
Ovsem ZAJISTIT to nemusi byt trivialni (ci mozne) a alokace na halde je pak jistejsi reseni. (Ale ono s zivotem objektu je stejne potreba VZDY zachazet opatrne a s rozvahou.)

B) 2. pro instance na halde/zasobniku/staticke plati stejna pravidla jako pro 4. - tedy muze to byt i na stacku, pokud ZAJISTIS, ze se to bude pouzivat spravne.

B) 3. char* nebere nic nijak - je to proste ukazatel na znak (ale v tvem pripade je mnohem lepsi pouzivat neco jako uint8_t* nebo jaky binarni format tam mas ulozeny), co se zajima o \0 jsou funkce pro praci s retezci (ktere bys na neretezce pouzivat prave kvuli tomu nemel, na to jsou funkce jine)

B) 4. vykaslat se na bezpecnost je az ta zcela posledni zoufala moznost a i tak to znamena, ze to mas resit jinak. Casto jde najit i jine reseni, ktere zajisti bezpecnost uz pred kritickou casti a pak se da ta kriticka optimalizovat mnohem lepe (myslim bezpecnost proti vnejsimu utoku - bezpecnost proti chybe programatora je neco zcela jineho). Pripomel bych zde Asimova, kde se v jedne povidce problem se tremi robotickymi zakony (ktere vyzaduji velky a slozity pozitronicky mozek) vyresi tim, ze se problem predefinuje tak, aby je reseni NEMOHLO porusit, ani kdyz o nich samo nevi - robot tak uzce zamereny, ze dela jen jednu vec a tu dela spravne nemusi poslouchat prikazy cloveka, nebot neni co by mu bylo mozno rozumne prikazovat atd. ... takze pro kriticke (nejen) casti pouzij stare zname osvedcene "rozdel a panuj" na tak male casti, ze bezpecnost pujde zajistit trivialne a nebudes mit s ni problemy.

Dobre testy jsou fajn (opravdu velice moc fajn, minimalne ti zajisti vetsi klid pri vetsich {o/u}pravach, ze ti nejaka zmena nerozbila neco v jine vzdalene casti), ale az na vyjimky se jimi neda pokryt vsechno, takze bezpecnost a spravnost je potreba resit uz pri navrhu a programovani, testy velice snadno neco zasadniho "prehlednou".

A rozhodne je dobre, ze se takovymahle vecma zabyvas. I kdybys dospel k ne zcela dokonalemu reseni, bude to vzdy vyrazne lepsi, nez to nechat plavat.

A jeste jednou duraznim, ze povazuju prehlednost a citelnost za NAPROSTO ZASADNI, zejmena v oblastech, kde musis tezce optimalizovat. Komentare a uhledny kod nestoji zadny vykon a zadne misto v pameti (a zdrojak, ktery by se ti nevesel na soucasne disky nemas jak rozumne napsat) a pomoci nich se da udelat rozumne citelny i samomodifikujici se kod v assembleru, kde se pouziva treba i skakani doprostred instrukci (coz je temna magie, kterou ted ROZHODNE NECHCES zkouset).

Rozdeleni projektu na vic casti vyrazne pomaha, stejne jako foldovani - zlate pravidlo je, ze bys nemel pokud mozno nikdy pracovat na casti vyrazne vetsi, nez se ti vejde na jednu obrazovku :-)

A jako samozrejmost beru vydatne pouzivani verzovaciho systemu (ja pouzivam (a povazuju za nejlepsi) GIT, ale jsou i jine a rozhodne lepsi nejaky, nez zadny). Diky nemu nemam problem delat i brutalni prestavby, zkouset divoke napady a testovat zbesile hypotezy - klidne muzu jit i nekolika smery naraz s vedomim toho, ze se muzu kdykoli vratit k cemukoli funkcnimu, nebo naopak klidne nekdy v budoucnu uspesne cesty zase pospojovat, nebo z nich vybrat jen to, co je na nich dobre. Zalozeni nove vetve nestoji nic (zlomek sekundy a soubor o delce par znaku) a svoboda, kterou to poskytuje je uzasna. (Pokud to snad jeste nedelas, tak zacni hned, investovana prace i cas se ti velice rychle vrati)

A samozrejme kommituju zmeny velice casto, kdykoli neco chodi, kdykoli narazim na chybu ci problem, kdykooli odchazim od pocitace na vic nez par minut (napr. na obed) a kdykoli me napadne. Pokud dojdu rozumneho vysledku, neni problem shrnout celou vetev do jednoho (ci vice) uceleneho kommitu, ktery vypada, ze jsem byl osvicen zhury a napsal danou cast na prvni pokus zcela bez chyb - a nasledne tu vetev zahodit. Pokud naopak rozbiju neco, co uz chodilo, tak se par disekcema dostanu presne na misto, ve kterem ta chyba vznikla a kdyz ty zmeny jsou jen par radku, tak neni problem chybu najit a odstranit. (dnes treba na jedne z veci, co jsem delal 13 verzi behem hodiny a pul, tedy v prumeru kazdych 7 minut, vcetne kompletniho releasu, instalace a testu - proste pohoda)

(BTW: vetsina z vyse napsaneho se vztahuje na vsechny jazyky a prostredi)

C++ je pomerne pekný jazyk, hlavne jeho posledné verzie. Nanešťastie je aj pomerne komplikovaný, hlavne kvôli histórii, ktorú si so sebou nesie. Ak by som mal teraz niečo programovať s použitím natívneho kompilovaného jazyka, kde by pred pár rokmi bolo C++ alebo C jasnou voľbou, tak by som si zvolil Rust a vyhol by som sa tým množstvu problémov s riadením prístupu k pamäti.

Inak v princípe myslíš dobre.

Weak pointer sa používa, keď nemáš pod kontrolou životnosť objektu, na ktorý odkazuješ. Je to tiež dobré napr. na to, aby si sa vyhol kruhovým referenciám. Typickým príkladom je vzťah rodič-dieťa, keď chceš, aby jeden rodič mohol ukazovať na viacero detí, a súčasne, aby každé dieťa ukazovalo na svojho rodiča. Toto vyzerá na celkom dobrý článok.

Ešte by som k tým pravidlám pridal RAII, pravidlo troch/piatich/nula a použitie nástrojov ako Valgrind alebo Application Verifier.

Zpět do poradny Odpovědět na původní otázku Nahoru