it-swarm-eu.dev

Nejhorší praktiky v C ++, časté chyby

Po přečtení tento slavný výkřik Linuse Torvalds jsem přemýšlel, co ve skutečnosti jsou všechna úskalí pro programátory v C++. Výslovně nehovořím o překlepech nebo špatném průběhu programu, jak je zpracováno v tato otázka a její odpovědi , ale k více chybám na vysoké úrovni, které nejsou detekovány kompilátorem a nevedou ke zjevným chybám v první spuštění, úplné chyby designu, věci, které jsou nepravděpodobné v C, ale pravděpodobně budou provedeny v C++ nováčky, kteří nerozumí plným důsledkům jejich kódu.

Vítám také odpovědi poukazující na obrovské snížení výkonu tam, kde by se to obvykle neočekávalo. Příklad toho, co mi jeden z mých profesorů kdysi řekl o generátoru paralyzátoru LR (1), který jsem napsal:

Použili jste příliš mnoho případů zbytečné dědičnosti a virtuality. Dědičnost činí návrh mnohem komplikovanějším (a neefektivním z důvodu subsystému RTTI (odvození typu run-time)), a proto by se měl používat pouze tam, kde to dává smysl, např. pro akce v tabulce analýzy. Protože šablony intenzivně využíváte, prakticky dědictví prakticky nepotřebujete. “

35
Felix Dombek

Torvalds zde mluví ze zadku.


Dobře, proč mluví ze zadku:

Zaprvé, jeho chvástání není nic, ale chvástání. Zde je velmi málo skutečného obsahu. Jediný důvod, proč je opravdu slavný nebo dokonce mírně respektovaný, je ten, že byl vytvořen bohem Linuxu. Jeho hlavním argumentem je, že C++ je kecy a rád ciká C++ lidi. Na to samozřejmě není žádný důvod a každý, kdo to považuje za rozumný argument, je mimo konverzaci stejně.

Pokud jde o to, co by mohlo být zářil jako jeho nejobjektivnější body:

  • STL a Boost jsou naprosté blbosti <- cokoli. Jsi idiot.
  • STL a Boost způsobují nekonečné množství bolesti <- směšné. Je zřejmé, že je záměrně přehnaný, ale jaké je jeho skutečné prohlášení? Nevím. Když způsobíte zvracení kompilátoru v Duchu nebo něco podobného, ​​je více než triviálně obtížné přijít na to, ale není to víc či méně obtížné zjistit, než odladit UB způsobené zneužitím konstrukcí C, jako je neplatné *.
  • Abstraktní modely podporované C++ jsou neefektivní. <- Jako co? Nikdy se nerozšiřuje, nikdy neposkytuje žádné příklady toho, co tím myslí, jen to říká. BFD. Protože nemůžu říct, o čem mluví, nemá smysl se pokusit prohlášení vyvrátit. Je to běžná mantru C bigotů, ale to ji nečiní srozumitelnější nebo srozumitelnější.
  • Správné použití C++ znamená, že se omezíte na aspekty C. <- Vlastně to dělá kód WORSE C++, takže stále nevím, o čem WTF mluví.

Torvalds v podstatě mluví ze zadku. Neexistuje žádný srozumitelný argument o ničem. Očekávat vážné vyvrácení takových nesmyslů je prostě hloupé. Říká se mi, abych „expandovala“ na vyvrácení něčeho, co by se od ní očekávalo, kdybych to tam, kde jsem to řekla. Pokud ano, upřímně se podívejme na to, co Torvalds řekl, že vidíš, že vlastně nic neřekl.

Jen proto, že Bůh říká, že to neznamená, že to dává nějaký smysl nebo by se mělo brát vážněji, než kdyby to řekl nějaký náhodný bozo. Po pravdě řečeno, Bůh je jen další náhodný bozo.


Odpověď na aktuální otázku:

Pravděpodobně nejhorší a nejběžnější špatná C++ praxe je zacházet s ní jako C. Pokračující používání funkcí C API, jako je printf, získává (také považováno za špatné v C), strtok atd. ... nejenom selhává s využitím poskytované energie pomocí těsnějšího typu systému nevyhnutelně vedou k dalším komplikacím, když se snaží interagovat s „skutečným“ kódem C++. Takže v podstatě udělejte pravý opak toho, co Torvalds radí.

Naučte se využívat možnosti STL a Boost, abyste získali další zjišťování chyb při kompilaci a usnadnili vám život jiným obecným způsobem (například tokenizér boost je typově bezpečný a lepší rozhraní). Je pravda, že se budete muset naučit číst chyby šablon, což je zpočátku skličující, ale (podle mých zkušeností je to stejně upřímně mnohem jednodušší než pokusit se odladit něco, co generuje nedefinované chování během běhu, což vytváří C api) docela snadné.

Neříkej, že C není tak dobré. Samozřejmě se mi líbí C++ lépe. Programátoři C jako C lépe. Ve hře jsou kompromisy a subjektivní zájmy. Je tu také spousta dezinformací a FUD vznášející se kolem. Řekl bych, že kolem C++ se vznáší více FUD a dezinformací, ale v tomto ohledu jsem zkreslený. Například problémy „nadýmání“ a „výkonu“ C++ pravděpodobně ve většině případů ve skutečnosti nejsou hlavními problémy a určitě jsou sfouknuty z proporcí reality.

Co se týče otázek, na které váš profesor odkazuje, nejedná se o jedinečný jazyk pro C++. V OOP (a v obecném programování) chcete dávat přednost kompozici před dědičností. Dědičnost je nejsilnější možný vazební vztah, který existuje ve všech OO jazycích. C++ přidává ještě jednu silnější přátelství. Polymorfní dědičnost by měla být použita k reprezentaci abstrakcí a „is-a“ vztahy, nikdy by neměly být použity pro opakované použití. Toto je druhá největší chyba, kterou můžete udělat v C++, a je to docela velká chyba, ale není ani zdaleka jedinečná k jazyku. Příliš složité dědické vztahy můžete vytvořit také v C # nebo Java a budou mít stejné problémy.

69
Edward Strange

Vždycky jsem si myslel, že nebezpečí C++ jsou velmi přehnaná nezkušenými C s programátory Classes.

Ano, C++ je těžší vyzvednout než něco jako Java, ale pokud programujete pomocí moderních technik, je docela snadné psát robustní programy. Upřímně řečeno, nemám to mnohem náročnější na časové programování v C++ než v jazycích, jako je Java, a často se mi zdá, že mi chybí určité C++ abstrakce, jako jsou šablony a RAII, když navrhuji v jiných jazycích .

To znamená, že i po letech programování v C++ udělám tu a tam opravdu hloupou chybu, kterou by nebylo možné v jazyce vyšší úrovně. Jeden běžný úskalí v C++ ignoruje životnost objektu: v Java a C # se obvykle nemusíte starat o životnost objektu *, protože všechny objekty existují na haldě a jsou pro vás spravovány) od kouzelného sběratele odpadu.

Nyní, v moderním C++, obvykle, se nemusíte starat ani o život objektu. Máte destruktory a inteligentní ukazatele, které pro vás spravují životnost objektů. 99% času, to funguje skvěle. Ale tu a tam se dostanete zašroubováváním visícího ukazatele (nebo odkazu). Například právě nedávno jsem měl objekt (řekněme to Foo), který obsahoval interní referenční proměnnou k jinému objektu ( Řekněme tomu Bar). V jednu chvíli jsem hloupě uspořádal věci tak, aby Bar vyšel z rozsahu dříve, než Foo, ale destruktor Foo nakonec skončil vyvoláním členské funkce Bar. Netřeba dodávat, že se věci neukázaly dobře.

Teď za to nemůžu vinit C++. Byl to můj vlastní špatný design, ale jde o to, že by se něco takového nestalo ve spravovaném jazyce na vyšší úrovni. I s inteligentními ukazateli a podobně, někdy musíte mít povědomí o životnosti objektu.


* Pokud je spravovaným zdrojem paměť, je to.

19
Charles Salvia

Nadužívání bloků try/catch.

File file("some.txt");
try
{
  /**/

  file.close();
}
catch(std::exception const& e)
{
  file.close();
}

Toto obvykle pramení z jazyků jako Java a lidé budou argumentovat, že C++ postrádá klauzuli finalize.

Tento kód však vykazuje dvě čísla:

  • Je třeba vytvořit file před try/catch, Protože ve skutečnosti close nemůžete ve skutečnosti vytvořit soubor, který v catch neexistuje. To vede k „úniku oboru“ v tom, že file je viditelný po uzavření. Můžete přidat blok, ale ...: /
  • Pokud někdo přijde a přidá return uprostřed rozsahu try, soubor nebude uzavřen (což je důvod, proč lidé děvčata o nedostatku klauzule finalize)

V C++ však máme mnohem účinnější způsoby řešení tohoto problému, které:

  • Java finalize
  • C # 'using
  • Go's defer

Máme RAII, jehož opravdu zajímavá vlastnost se nejlépe shrnuje jako SBRM (Scoped Bound Resources Management).

Vytvořením třídy tak, aby její destruktor vyčistil prostředky, které vlastní, nezatěžujeme náklady na správu zdroje na každého jeho uživatele !

Toto je the funkce, kterou mi chybí v jakémkoli jiném jazyce, a pravděpodobně ten, který je nejvíce zapomenutý.

Pravdou je, že v C++ je třeba jen zřídka napsat blok try/catch, S výjimkou nejvyšší úrovně, aby nedošlo k ukončení bez protokolování.

13
Matthieu M.

Rozdíl v kódu obvykle souvisí spíše s programátorem než s jazykem. Obzvláště dobrý programátor C++ a programátor C přijdou k podobně dobrým (i když jiným) řešením. Nyní je C jednodušší jazyk (jako jazyk), což znamená, že existuje méně abstrakcí a větší viditelnost toho, co kód ve skutečnosti dělá.

Část jeho výkřiku (on je známý pro jeho výkřiky proti C + +) je založen na skutečnosti, že více lidí vezme C++, a psát kód, aniž by ve skutečnosti pochopil, co některé z abstrakcí skrývají a činí špatné předpoklady.

Jednou běžnou chybou, která vyhovuje vašim kritériím, je nerozumět tomu, jak konstruktéři kopií pracují při práci s přidělenou pamětí ve vaší třídě. Ztratil jsem počet času, který jsem strávil opravováním havárií nebo úniků paměti, protože „noob“ vložil své objekty do mapy nebo vektoru a nenapsal konstruktéry a destruktory kopií správně.

Bohužel C++ je plná „skrytých“ gotchas jako je tato. Ale stěžovat si na to je jako stěžovat si, že jsi šel do Francie a nechápal, co lidé říkají. Pokud se tam chystáte, naučte se jazyk.

9
Henry

C++ umožňuje celou řadu funkcí a stylů programování, ale to neznamená, že se jedná o skutečně dobré způsoby použití C++. A ve skutečnosti je neuvěřitelně snadné používat C++ nesprávně.

Musí to být správně se učit a rozumět , pouhé učení se tím, že to děláte (nebo používáte, jako by někdo používal nějaký jiný jazyk), povede k neúčinnému a náchylnému kódu.

6
Dario

No ... Pro začátečníky si můžete přečíst C++ FAQ Lite

Poté několik lidí vytvořilo kariéru, která píše knihy o složitosti jazyka C++:

Herb Sutter a Scott Meyers .

Co se týče Torvaldsovy chabé podstaty ... přijďte na lidi, vážně: Žádný jiný jazyk tam tolik inkoustu nevylil tolik, že se vypořádal s nuancemi jazyka. Vaše Python & Ruby & Java knihy jsou zaměřeny na psaní aplikací) ... vaše knihy C++ se zaměřují na hloupé jazykové funkce/tipy/pasti.

4
red-dirt

Příliš silné templing nemusí zpočátku způsobit chyby. Postupem času však budou lidé muset tento kód upravit a budou mít těžké pochopení obrovské šablony. To je, když chyby vstoupí - nedorozumění způsobí „kompiluje a spustí“ komentáře, které často vedou k téměř, ale ne zcela správnému kódu.

Obecně, když vidím, jak dělám třístupňovou hlubokou generickou šablonu, zastavím se a přemýšlím, jak ji lze zredukovat na jednu. Tento problém je často vyřešen extrahováním funkcí nebo tříd.

3
Michael K

Varování: toto není skoro tolik odpovědi jako kritika přednášky, na kterou „neodborný uživatel“ ve své odpovědi odkazuje.

Jeho prvním hlavním bodem je (údajně) „neustále se měnící standard“. Ve skutečnosti se příklady, které uvádí, týkají změn v C++ předtím, než existoval standard. Od roku 1998 (kdy byla dokončena první norma C++) byly změny jazyka poměrně minimální - ve skutečnosti by mnozí tvrdili, že skutečným problémem je, že mělo být více změn vyrobeno. Jsem si docela jistý, že veškerý kód, který vyhovoval původnímu standardu C++, stále odpovídá současnému standardu. I když je to poněkud méně jisté, pokud se něco rychle (a docela neočekávaně) nezmění, to samé bude do značné míry pravdivé i s nadcházejícím standardem C++ (teoreticky veškerý kód, který používal export se zlomí, ale prakticky žádný neexistuje; z praktického hlediska to není problém). Dokážu vymyslet několik dalších jazyků, operačních systémů (nebo mnoho jiných věcí souvisejících s počítačem), které by mohly takový nárok vyvolat.

Poté přechází do „neustále se měnících stylů“. Většina jeho bodů je opět velmi blízko nesmyslům. Pokouší se charakterizovat for (int i=0; i<n;i++) jako „old and busted“ a for (int i(0); i!=n;++i) „new hotness“. Realita je taková, že i když existují typy, pro které by takové změny mohly mít smysl, pro int to nijak nezmění - a dokonce i když něco můžete získat, je jen zřídka nutné napsat dobrý nebo správný kód. Dokonce i v nejlepším případě si dělá kopec z kopce.

Jeho další tvrzení je, že C++ se „optimalizuje nesprávným směrem“ - konkrétně, že zatímco připouští, že používání dobrých knihoven je snadné, tvrdí, že C++ „psaní dobrých knihoven téměř znemožňuje“. Zde věřím, že je jednou z jeho nejzákladnějších chyb. Ve skutečnosti je psaní dobrých knihoven pro téměř jakýkoli jazyk extrémně obtížné. Psaní dobré knihovny vyžaduje naprosté minimum porozumění některé problémové doméně tak dobře, aby váš kód fungoval pro velké množství možných aplikací v této doméně (nebo s ní souvisejících). Většina toho, co C++ opravdu dělá, je „zvednout lištu“ - poté, co uvidíme, o kolik lepší knihovna může být, jsou lidé zřídka ochotni se vrátit k psaní toho druhu, co by jinak měli. Ignoruje také skutečnost, že několik opravdu dobrých kodérů píše docela málo knihoven, které pak mohou být použity (snadno, jak připouští) „my ostatní“. To je opravdu případ, kdy „to není chyba, je to funkce.“

Nesnažím se zasáhnout každý bod v pořadí (to by mělo stránky), ale přeskočit přímo k jeho konečnému bodu. Cituje Bjarne jako: „Optimalizace celého programu může být použita k odstranění nevyužitých tabulek virtuálních funkcí a dat RTTI. Taková analýza je zvláště vhodná pro relativně malé programy, které nepoužívají dynamické propojení.“

Kritizuje to tvrzením, že nepodporuje tvrzení, že „Toto je opravd těžký problém“, dokonce i tak daleko, že ho porovná s problémem zastavení. Ve skutečnosti to není nic podobného - ve skutečnosti linker zahrnutý v Zortech C++ (do značné míry první C++ kompilátor pro MS-DOS, v 80. letech) to udělal . Je pravda, že je těžké si být jistý, že byl odstraněn každý kousek možná cizích dat, ale stále je docela rozumné dělat docela férovou práci.

Bez ohledu na to je však mnohem důležitějším bodem to, že to je pro většinu programátorů v každém případě naprosto irelevantní. Jak my z nás, kteří rozebrali docela dost kódu, vědí, že pokud nepíšete assemblerový jazyk bez knihoven, vaše spustitelné soubory téměř jistě obsahují velké množství „věcí“ (v typických případech kód i data), které vy pravděpodobně ani o tom ani nemluvě, nemluvě o použití ve skutečnosti. Pro většinu lidí to většinou nezáleží - pokud se nevyvíjíte pro nejmenší vestavěné systémy, tato dodatečná spotřeba úložiště je prostě irelevantní.

Nakonec je pravda, že tento výkřik má trochu podstatnější podstatu než Linusova idiotství - ale to mu dává přesně to zatracení se slabou chválou, kterou si zaslouží.

2
Jerry Coffin

Jako programátor C, který musel kvůli nevyhnutelným okolnostem kódovat v C++, je zde moje zkušenost. Existuje jen velmi málo věcí, které používám, a to je C++ a většinou se držím C. Hlavním důvodem je to, že C++ moc nerozumím. Neměl/a jsem mentora, který by mi ukázal složitosti jazyka C++ a jak do něj napsat dobrý kód. A bez vedení velmi dobrým kódem C++ je extrémně obtížné psát dobrý kód v jazyce C++. IMHO je to největší nevýhoda C++, protože dobré C++ kodéry ochotné držet začátečníky je těžké přijít.

Některé z výkonů, které jsem viděl, jsou obvykle kvůli alokaci magické paměti STL (ano, můžete změnit alokátor, ale kdo to udělá, když začne s C++?). Obvykle slyšíte argumenty expertů C++, že vektory a pole nabízejí podobný výkon, protože vektory používají pole interně a abstrakce je super účinná. Zjistil jsem, že to v praxi platí pro přístup k vektorům a úpravu existujících hodnot. To však neplatí pro přidání nového záznamu, konstrukce a ničení vektorů. gprof ukázal, že kumulativně 25% času na aplikaci bylo věnováno vektorovým konstruktérům, destruktorům, memmove (pro přemístění celého vektoru pro přidání nového prvku) a dalším přetíženým vektorovým operátorům (jako ++).

Ve stejné aplikaci byl vektor něcoSmall použit k reprezentaci něčeho Velkého. V něčem velkém nebylo třeba náhodného přístupu k něčemu velkému. Místo seznamu byl stále použit vektor. Důvod, proč byl použit vektor? Protože původní kodér byl obeznámen s řadou jako syntaxe vektorů a ne příliš obeznámen s iterátory potřebnými pro seznamy (ano, je z C pozadí). Pokračuje v dokazování, že k získání správného C++ je vyžadována řada pokynů odborníků. C nabízí tak málo základních konstrukcí s absolutně žádnou abstrakcí, že to dokážete mnohem snadněji než C++.

1
aufather

I když se mi líbí Linus Thorvalds, tento chvástání je bez podstaty - jen chvástání.

Pokud chcete vidět zdůvodněný výkřik, zde je jeden: „Proč je C++ špatné pro životní prostředí, způsobuje globální oteplování a zabíjí štěňata“ http://chaosradio.ccc.de/camp2007_m4v_1951.html Další materiál: http://www.fefe.de/c++/

Zábavná řeč, imho

0
user unknown

STL a boost jsou přenosné na úrovni zdrojového kódu. Hádám, o čem Linus mluví, je to, že C++ postrádá ABI (aplikační binární rozhraní). Musíte tedy zkompilovat všechny knihovny, se kterými jste propojeni, se stejnou verzí kompilátoru a se stejnými přepínači, nebo se sami omezit na C ABI na hranici dll. Zjistil jsem, že každý rok .. ale pokud nevyrábíte knihovny třetích stran, měli byste být schopni převzít kontrolu nad svým stavěcím prostředím. Zjistil jsem, že se omezím na C ABI nestojí za problém. Výhodou schopnosti předávat řetězce, vektory a inteligentní ukazatele z jednoho dll do druhého stojí za to, že při upgradu kompilátorů nebo změně přepínačů kompilátoru je třeba znovu vybudovat všechny knihovny. Zlatá pravidla, která dodržuji, jsou:

-Zadejte znovu použít rozhraní, ne implementaci

-Oddělit agregaci nad dědičností

-Podporujte, pokud je to možné, bezplatné funkce metodám členů

- Vždy používejte idiom RAII, aby byl váš kód silně výjimečný. Vyvarujte se pokusu chytit.

- Používejte inteligentní ukazatele, vyhýbejte se nahým (neznámým) ukazatelům

-Předložte sémantiku hodnoty referenční sémantice

-Nevytvářejte kolo, použijte stl a boost

-Použijte idiom Pimplu ke skrytí soukromých a/nebo k vytvoření brány překladače

0
user16642