Nachdem ich diese berühmte Parole von Linus Torvalds gelesen hatte , fragte ich mich, was eigentlich die Tücken für Programmierer in C ++ sind. Ich beziehe mich ausdrücklich nicht auf Tippfehler oder fehlerhafte Programmabläufe, wie sie in dieser Frage und ihren Antworten behandelt werden , sondern auf übergeordnete Fehler, die vom Compiler nicht erkannt werden und keine offensichtlichen Fehler beim ersten Start verursachen, vollständige Entwurfsfehler. Dinge, die in C unwahrscheinlich sind, aber wahrscheinlich in C ++ von Neulingen erledigt werden, die die vollständigen Auswirkungen ihres Codes nicht verstehen.
Ich begrüße auch Antworten, die auf einen enormen Leistungsabfall hinweisen, der normalerweise nicht zu erwarten ist. Ein Beispiel dafür, was mir einer meiner Professoren einmal über einen LR (1) -Parsergenerator erzählt hat:
Sie haben etwas zu viele Instanzen nicht benötigter Vererbung und Virtualität verwendet. Vererbung erschwert einen Entwurf erheblich (und ist aufgrund des RTTI-Subsystems (Runtime Type Inference) ineffizient) und sollte daher nur dort verwendet werden, wo dies sinnvoll ist, z. B. für die Aktionen in der Analysetabelle. Da Sie Vorlagen intensiv nutzen, benötigen Sie praktisch keine Vererbung. "
virtual
functions ergeben, oder?dynamic_cast
soll oder nicht, und einige andere Dinge, aber die Reflexion deckt noch viel mehr ab, einschließlich der Möglichkeit, Informationen zu Mitgliedsattributen oder -funktionen abzurufen, die nicht erfolgreich sind in C ++ vorhanden.Antworten:
Torvalds redet hier aus seinem Arsch.
OK, warum redet er aus seinem Arsch:
Zuallererst ist sein Schimpfen wirklich nichts, ABER Schimpfen. Hier gibt es sehr wenig aktuellen Inhalt. Der einzige Grund, warum es wirklich berühmt oder sogar geachtet ist, ist, dass es vom Linux-Gott gemacht wurde. Sein Hauptargument ist, dass C ++ Mist ist und er es mag, C ++ Leute wütend zu machen. Es gibt natürlich überhaupt keinen Grund, darauf zu antworten, und jeder, der dies für ein vernünftiges Argument hält, ist sowieso nicht zu diskutieren.
Was als seine objektivsten Punkte angesehen werden könnte:
Grundsätzlich redet Torvalds aus seinem Arsch. Es gibt keine verständlichen Argumente für irgendetwas. Eine ernsthafte Widerlegung eines solchen Unsinns zu erwarten, ist einfach albern. Mir wird gesagt, ich solle eine Widerlegung von etwas "erweitern", von dem ich erwarten würde, dass es erweitert wird, wenn ich es dort finde, wo ich es gesagt habe. Wenn Sie sich ehrlich ansehen, was Torvalds gesagt hat, werden Sie feststellen, dass er eigentlich nichts gesagt hat.
Nur weil Gott sagt, dass es nicht bedeutet, dass es irgendeinen Sinn macht oder ernst genommen werden sollte, als wenn irgendein zufälliger Bozo es gesagt hätte. Um ehrlich zu sein, Gott ist nur ein weiterer Zufallsbozo.
Auf die eigentliche Frage antworten:
Die wahrscheinlich schlimmste und häufigste schlechte C ++ - Praxis besteht darin, sie wie C zu behandeln. Die fortgesetzte Verwendung von C-API-Funktionen wie printf, gets (gilt auch in C als schlecht), strtok usw. kann die bereitgestellte Leistung nicht nur nicht nutzen Durch das engere Typensystem führen sie unweigerlich zu weiteren Komplikationen, wenn versucht wird, mit "echtem" C ++ - Code zu interagieren. Machen Sie also genau das Gegenteil von dem, was Torvalds empfiehlt.
Erfahren Sie, wie Sie STL und Boost nutzen, um Fehler beim Kompilieren schneller zu erkennen und Ihr Leben auf andere, allgemeine Weise zu vereinfachen (der Boost-Tokenizer ist beispielsweise sowohl typensicher als auch eine bessere Benutzeroberfläche). Es ist wahr, dass Sie lernen müssen, wie man Template-Fehler liest, was zunächst einschüchternd ist, aber (meiner Erfahrung nach) ist es ehrlich gesagt viel einfacher, etwas zu debuggen, das während der Laufzeit undefiniertes Verhalten erzeugt, das die C-API erzeugt ganz einfach zu machen.
Um nicht zu sagen, dass C nicht so gut ist. Ich mag C ++ natürlich besser. C-Programmierer mögen C besser. Es gibt Kompromisse und subjektive Vorlieben. Es gibt auch viele Fehlinformationen und FUD. Ich würde sagen, dass es mehr FUD und Fehlinformationen über C ++ gibt, aber in dieser Hinsicht bin ich voreingenommen. Zum Beispiel sind die Probleme "Aufblähen" und "Leistung", die C ++ angeblich hat, die meiste Zeit keine größeren Probleme und werden mit Sicherheit aus den Proportionen der Realität gerissen.
Die Probleme, auf die sich Ihr Professor bezieht, sind nicht nur in C ++ zu finden. In OOP (und in der generischen Programmierung) möchten Sie die Komposition der Vererbung vorziehen. Vererbung ist die stärkste mögliche Kopplungsbeziehung, die in allen OO-Sprachen besteht. C ++ fügt eine weitere hinzu, die stärker ist, Freundschaft. Polymorphe Vererbung sollte verwendet werden, um Abstraktionen und "Ist-Eine" -Beziehungen darzustellen. Sie sollte niemals zur Wiederverwendung verwendet werden. Dies ist der zweitgrößte Fehler, den Sie in C ++ machen können, und es ist ein ziemlich großer Fehler, der jedoch für die Sprache alles andere als eindeutig ist. Sie können auch in C # oder Java übermäßig komplexe Vererbungsbeziehungen erstellen, bei denen genau dieselben Probleme auftreten.
quelle
Ich habe immer gedacht, dass die Gefahren von C ++ von unerfahrenen C-Klassen-Programmierern stark übertrieben wurden.
Ja, C ++ ist schwieriger zu erlernen als Java, aber wenn Sie mit modernen Techniken programmieren, ist es ziemlich einfach, robuste Programme zu schreiben. Ehrlich gesagt , ich habe nicht , dass viele schwieriger eine Zeitprogrammierung in C ++ als ich in Sprachen wie Java zu tun, und ich finde ich oft bestimmen C ++ Abstraktionen wie Vorlagen und RAII fehlen , wenn ich in anderen Sprachen entwerfen.
Das heißt, auch nach Jahren des Programmierens in C ++ mache ich hin und wieder einen wirklich dummen Fehler, der in einer höheren Sprache nicht möglich wäre. Eine häufige Gefahr in C ++ ist das Ignorieren der Objektlebensdauer: In Java und C # müssen Sie sich im Allgemeinen nicht um die Objektlebensdauer * kümmern, da alle Objekte auf dem Heap vorhanden sind und von einem magischen Garbage Collector für Sie verwaltet werden.
Nun, in der modernen C ++, in der Regel brauchen Sie nicht viel über das Objekt Lebenszeit entweder zu kümmern. Sie haben Destruktoren und intelligente Zeiger, die die Lebensdauer von Objekten für Sie verwalten. In 99% der Fälle funktioniert dies wunderbar. Aber hin und wieder werden Sie von einem baumelnden Zeiger (oder einer Referenz) verarscht. Zum Beispiel hatte ich kürzlich ein Objekt (nennen wir es
Foo
), das eine interne Referenzvariable zu einem anderen Objekt enthielt (nennen wir esBar
). Irgendwann habe ich dummerweise Dinge so arrangiert, dassBar
sie vorherFoo
nicht mehr in Frage kamen , und doch hatFoo
der Destruktor eine Member - Funktion von aufgerufenBar
. Natürlich lief es nicht gut.Nun, ich kann C ++ nicht wirklich dafür verantwortlich machen. Es war mein eigenes schlechtes Design, aber der Punkt ist, dass so etwas in einer übergeordneten, verwalteten Sprache nicht passieren würde. Selbst mit intelligenten Zeigern und Ähnlichem müssen Sie sich manchmal der Objektlebensdauer bewusst sein.
* Wenn es sich bei der verwalteten Ressource um Speicher handelt, ist dies der Fall.
quelle
Der Unterschied im Code hängt normalerweise mehr mit dem Programmierer als mit der Sprache zusammen. Insbesondere ein guter C ++ - Programmierer und ein C-Programmierer kommen beide zu ähnlich guten (auch wenn unterschiedlichen) Lösungen. Jetzt ist C eine einfachere Sprache (als Sprache) und das bedeutet, dass es weniger Abstraktionen gibt und mehr Einblick in das, was der Code tatsächlich tut.
Ein Teil seines Rants (er ist bekannt für seine Rants gegen C ++) basiert auf der Tatsache, dass mehr Leute sich mit C ++ auseinandersetzen und Code schreiben, ohne wirklich zu verstehen, was einige der Abstraktionen verbergen und falsche Annahmen treffen.
quelle
std::vector<bool>
Wert zu ändern?for ( std::vector<bool>::iterator it = v.begin(), end = v.end(); it != end; ++it ) { *it = !*it; }
? Was ist in abstrahiert*it = !*it;
?std::vector<bool>
ist ein bekannter Fehler, aber es ist ein wirklich gutes Beispiel dafür, was diskutiert wird: Abstraktionen sind gut, aber man muss aufpassen, was sie verbergen. Das gleiche kann und wird im Benutzercode passieren. Für den Anfang habe ich gesehen, dass sowohl in C ++ als auch in Java Ausnahmen verwendet werden, um die Ablaufsteuerung durchzuführen, und Code, der wie ein Aufruf einer Verschachtelungsfunktion aussieht, der eigentlich ein Ausnahmebefehlsprogramm für Rettungsaktionen ist:void endOperation();
implementiert alsthrow EndOperation;
. Ein guter Programmierer wird diese überraschenden Konstrukte vermeiden , aber Tatsache ist, dass Sie sie finden können.Überbeanspruchung von
try/catch
Blöcken.Dies ist normalerweise auf Sprachen wie Java zurückzuführen, und die Leute werden argumentieren, dass C ++ eine
finalize
Klausel fehlt .Dieser Code weist jedoch zwei Probleme auf:
file
vor dem bauentry/catch
, weil man eigentlichclose
keine Datei kann, in der es keine gibtcatch
. Dies führt zu einem "Zielfernrohrleck",file
das nach dem Schließen sichtbar wird. Sie können einen Block hinzufügen, aber ...: /return
mitten imtry
Gültigkeitsbereich eine hinzufügt , wird die Datei nicht geschlossen (weshalb die Leute sich über das Fehlen einerfinalize
Klausel beschweren).In C ++ haben wir jedoch wesentlich effizientere Möglichkeiten, mit diesem Problem umzugehen:
finalize
using
defer
Wir haben RAII, dessen wirklich interessante Eigenschaft sich am besten als
SBRM
(Scoped Bound Resources Management) zusammenfassen lässt.Indem wir die Klasse so gestalten, dass ihr Destruktor die Ressourcen bereinigt, die sie besitzt, müssen wir nicht jeden einzelnen Benutzer mit der Verwaltung der Ressourcen beauftragen!
Dies ist die Funktion, die ich in jeder anderen Sprache vermisse und wahrscheinlich die, die am meisten vergessen wird.
Die Wahrheit ist, dass es selten nötig ist, einen
try/catch
Block in C ++ zu schreiben , abgesehen von der obersten Ebene, um eine Beendigung ohne Protokollierung zu vermeiden.quelle
fopen
undfclose
hier ersetzen .) RAII ist die "richtige" Art, Dinge hier zu tun, aber es ist unpraktisch für Leute, die C-Bibliotheken aus C ++ verwenden möchten .File file("some.txt");
und das ist es (neinopen
, neinclose
, neintry
...)Ein häufiger Fehler, der Ihren Kriterien entspricht, besteht darin, nicht zu verstehen, wie Kopierkonstruktoren im Umgang mit dem zugewiesenen Speicher in Ihrer Klasse funktionieren. Ich habe die Anzahl der Zeit, die ich damit verbracht habe, Abstürze oder Speicherlecks zu beheben, verloren, weil ein 'Noob' seine Objekte in eine Karte oder einen Vektor gesteckt hat und Kopierkonstruktoren und Destruktoren nicht richtig geschrieben hat.
Leider steckt C ++ voller "versteckter" Fallstricke. Aber sich darüber zu beschweren ist wie sich zu beschweren, dass du nach Frankreich gegangen bist und nicht verstehen konntest, was die Leute sagten. Wenn Sie dorthin gehen, lernen Sie die Sprache.
quelle
C ++ ermöglicht eine Vielzahl von Funktionen und Programmierstilen. Dies bedeutet jedoch nicht, dass dies eine gute Möglichkeit für die Verwendung von C ++ ist. Tatsächlich ist es unglaublich einfach, C ++ falsch zu verwenden.
Es muss richtig erlernt und verstanden werden . Nur das Erlernen durch Ausführen (oder die Verwendung wie eine andere Sprache) führt zu ineffizientem und fehleranfälligem Code.
quelle
Nun ... Für den Anfang können Sie die lesen C ++ FAQ Lite
Dann haben mehrere Leute Karrieren aufgebaut und Bücher über die Feinheiten von C ++ geschrieben:
Herb Sutter und Scott Meyers nämlich.
Was die mangelnde Substanz von Torvalds angeht ... nehmen Sie es ernst: In keiner anderen Sprache wurde so viel Tinte auf den Umgang mit den Nuancen der Sprache verschüttet. Ihre Python & Ruby & Java-Bücher konzentrieren sich alle auf das Schreiben von Anwendungen ... Ihre C ++ - Bücher konzentrieren sich auf alberne Sprachfunktionen / Tipps / Traps.
quelle
Zu starkes Templating führt möglicherweise zunächst nicht zu Fehlern. Mit der Zeit müssen die Benutzer diesen Code jedoch ändern, und es fällt ihnen schwer, eine riesige Vorlage zu verstehen. In diesem Moment treten Fehler auf - Missverständnisse führen dazu, dass Kommentare "kompiliert und ausgeführt werden", die häufig zu einem fast, aber nicht ganz korrekten Code führen.
Wenn ich mir vorstelle, wie ich eine dreistufige, tiefe generische Vorlage mache, höre ich auf und überlege, wie ich sie auf eine reduzieren könnte. Oft wird das Problem durch Extrahieren von Funktionen oder Klassen gelöst.
quelle
Warnung: Dies ist bei weitem keine so gute Antwort wie eine Kritik an dem Vortrag, auf den "Benutzer unbekannt" in seiner Antwort verwiesen hat.
Sein erster Hauptpunkt ist der (angeblich) "sich ständig ändernde Standard". In Wirklichkeit beziehen sich die Beispiele, die er nennt, alle auf Änderungen in C ++, bevor es einen Standard gab. Seit 1998 (als der erste C ++ - Standard fertiggestellt wurde) waren die Änderungen an der Sprache recht gering - tatsächlich würden viele argumentieren, dass das eigentliche Problem darin besteht, dass weitere Änderungen hätten vorgenommen werden müssen. Ich bin mir ziemlich sicher, dass der gesamte Code, der dem ursprünglichen C ++ - Standard entspricht, weiterhin dem aktuellen Standard entspricht. Es wird zwar etwas weniger sicher, aber es sei denn, etwas ändert sich schnell (und ganz unerwartet), dasselbe gilt auch für den kommenden C ++ - Standard (theoretisch für den gesamten verwendeten Code)
export
kaputt gehen, aber es gibt praktisch keine. aus praktischer Sicht ist es kein Problem). Ich kann mir nur wenige andere Sprachen, Betriebssysteme (oder vieles andere im Zusammenhang mit Computern) vorstellen, die einen solchen Anspruch erheben können.Er geht dann in "ständig wechselnde Stile". Auch hier liegen die meisten seiner Punkte ziemlich nahe am Unsinn. Er versucht,
for (int i=0; i<n;i++)
als "alt und kaputt" undfor (int i(0); i!=n;++i)
"neue Schärfe" zu charakterisieren . Die Realität ist, dass es zwar Typen gibt, für die solche Änderungen sinnvoll sein könntenint
, es aber keinen Unterschied macht - und selbst wenn Sie etwas gewinnen könnten, ist es selten notwendig, guten oder korrekten Code zu schreiben. Selbst im besten Fall macht er aus einem Maulwurfshügel einen Berg.Seine nächste Behauptung ist, dass C ++ "in die falsche Richtung optimiert" - insbesondere, während er zugibt, dass die Verwendung guter Bibliotheken einfach ist, behauptet er, dass C ++ "das Schreiben guter Bibliotheken fast unmöglich macht". Hier glaube ich, ist einer seiner grundlegendsten Fehler. In Wirklichkeit Bibliotheken gute Schreiben für fast jede Sprache ist extrem schwierig. Das Schreiben einer guten Bibliothek setzt zumindest voraus, dass Sie sich mit einer Problemdomäne so gut auskennen, dass Ihr Code für eine Vielzahl möglicher Anwendungen in dieser Domäne (oder in Bezug auf diese Domäne) funktioniert. Das meiste, was C ++ wirklich tut, ist "die Messlatte höher legen " - nachdem man gesehen hat, wie viel besser eine Bibliothek sein kann , sind die Leute selten bereit, wieder die Art von Dreck zu schreiben, die sie sonst hätten.wirklich gute Programmierer schreiben einige Bibliotheken, die dann (wie er zugibt) von "dem Rest von uns" benutzt werden können. Dies ist wirklich ein Fall, in dem "das ist kein Fehler, es ist eine Funktion."
Ich werde nicht versuchen, jeden Punkt in der Reihenfolge zu treffen (das würde Seiten in Anspruch nehmen), sondern direkt zu seinem Endpunkt springen. Er zitiert Bjarne mit den Worten: "Durch die Optimierung des gesamten Programms können nicht verwendete virtuelle Funktionstabellen und RTTI-Daten entfernt werden. Diese Analyse eignet sich insbesondere für relativ kleine Programme, die keine dynamische Verknüpfung verwenden."
Er kritisiert dies, indem er die Behauptung, "Dies ist ein wirklich schweres Problem", nicht nur mit dem Problem des Stillstands vergleicht. In Wirklichkeit ist es nichts dergleichen - tatsächlich hat der Linker, der in Zortech C ++ (ziemlich genau der erste C ++ - Compiler für MS-DOS in den 1980er Jahren) enthalten war, dies getan. Es ist wahr, dass es schwierig ist, sicher zu sein, dass alle möglicherweise irrelevanten Daten beseitigt wurden, aber immer noch völlig vernünftig, um einen ziemlich fairen Job zu machen.
Unabhängig davon ist jedoch der viel wichtigere Punkt, dass dies für die meisten Programmierer in jedem Fall völlig irrelevant ist. Wie diejenigen von uns, die eine ganze Menge Code zerlegt haben, wissen, enthalten Ihre ausführbaren Dateien mit ziemlicher Sicherheit eine ganze Menge "Zeug" (sowohl Code als auch Daten, in typischen Fällen), die Sie schreiben, es sei denn, Sie schreiben Assemblersprache ohne Bibliotheken wahrscheinlich nicht einmal wissen, ganz zu schweigen von jemals tatsächlich mit. Für die meisten Menschen spielt es meistens keine Rolle - es sei denn, Sie entwickeln für die kleinsten eingebetteten Systeme, ist dieser zusätzliche Speicherverbrauch einfach irrelevant.
Am Ende ist es wahr, dass dieser Schimpanse ein bisschen mehr Substanz hat als Linus 'Idiotie - aber das gibt ihm genau das Verdammnis mit dem schwachen Lob, das es verdient.
quelle
Als C-Programmierer, der aufgrund unvermeidlicher Umstände in C ++ programmieren musste, habe ich folgende Erfahrungen gemacht. Es gibt nur sehr wenige Dinge, die ich benutze, nämlich C ++ und meistens C. Der Hauptgrund dafür ist, dass ich C ++ nicht so gut verstehe. Ich hatte / habe keinen Mentor, der mir die Feinheiten von C ++ zeigte und wie man guten Code darin schreibt. Und ohne Anleitung von einem sehr sehr guten C ++ - Code ist es extrem schwierig, guten Code in C ++ zu schreiben. Meiner Meinung nach ist dies der größte Nachteil von C ++, da gute C ++ - Programmierer, die bereit sind, Anfänger festzuhalten, nur schwer zu bekommen sind.
Einige der Performance-Hits, die ich normalerweise gesehen habe, sind auf die magische Speicherzuweisung von STL zurückzuführen (ja, Sie können den Allokator ändern, aber wer macht das, wenn er mit C ++ anfängt?). In der Regel hören Sie Argumente von C ++ - Experten, dass Vektoren und Arrays eine ähnliche Leistung bieten, da Vektoren intern Arrays verwenden und die Abstraktion sehr effizient ist. Ich habe festgestellt, dass dies in der Praxis für den Vektorzugriff und das Ändern vorhandener Werte zutrifft. Dies gilt jedoch nicht für das Hinzufügen eines neuen Eintrags, die Konstruktion und Zerstörung von Vektoren. gprof zeigte, dass insgesamt 25% der Zeit für eine Anwendung in Vektorkonstruktoren, Destruktoren, memmove (zum Verschieben des gesamten Vektors zum Hinzufügen neuer Elemente) und anderen überladenen Vektoroperatoren (wie ++) verbracht wurden.
In derselben Anwendung wurde der Vektor "somethingSmall" verwendet, um "somethingBig" darzustellen. Zufälliger Zugriff auf somethingSmall in somethingBig war nicht erforderlich. Es wurde immer noch ein Vektor anstelle einer Liste verwendet. Der Grund, warum Vektor verwendet wurde? Weil der ursprüngliche Codierer mit der Array-ähnlichen Syntax von Vektoren vertraut war und mit den für Listen benötigten Iteratoren nicht sehr vertraut war (ja, er hat einen C-Hintergrund). Es wird weiterhin bewiesen, dass eine Menge Anleitung von Experten erforderlich ist, um C ++ richtig zu machen. C bietet so wenig grundlegende Konstrukte ohne jegliche Abstraktion, dass Sie es viel einfacher als C ++ richtig machen können.
quelle
Obwohl ich Linus Thorvalds mag, ist dieses Geschwätz ohne Substanz - nur ein Geschwätz.
Wenn Sie einen begründeten Rant sehen möchten, ist hier einer: "Warum C ++ schlecht für die Umwelt ist, die globale Erwärmung verursacht und Welpen tötet" http://chaosradio.ccc.de/camp2007_m4v_1951.html Zusätzliches Material: http: // www .fefe.de / c ++ /
Ein unterhaltsames Gespräch, imho
quelle
STL und Boost sind auf Quellcode-Ebene portabel. Ich denke, was Linus meint, ist, dass C ++ kein ABI (Application Binary Interface) hat. Sie müssen also alle Bibliotheken, mit denen Sie verknüpfen, mit derselben Compilerversion und denselben Schaltern kompilieren oder sich an den DLL-Grenzen auf das C-ABI beschränken. Ich finde das auch ärgerlich. Aber wenn Sie keine Bibliotheken von Drittanbietern erstellen, sollten Sie in der Lage sein, die Kontrolle über Ihre Build-Umgebung zu übernehmen. Ich finde, dass es die Mühe nicht wert ist, mich auf das C ABI zu beschränken. Die Bequemlichkeit, Zeichenfolgen, Vektoren und intelligente Zeiger von einer DLL an eine andere übergeben zu können, ist die Mühe wert, alle Bibliotheken neu erstellen zu müssen, wenn Compiler aktualisiert oder Compiler-Schalter geändert werden. Die goldenen Regeln, denen ich folge, sind:
-Erben Sie die Wiederverwendung der Schnittstelle, nicht die Implementierung
-Bevorzugen Sie die Aggregation über die Vererbung
-Bevorzugen Sie nach Möglichkeit freie Funktionen den Methoden der Mitglieder
- Verwenden Sie immer die RAII-ID, um Ihren Code stark ausnahmesicher zu machen. Versuchen Sie nicht zu fangen.
-Vermeiden Sie mit intelligenten Zeigern nackte (nicht im Besitz befindliche) Zeiger
-Bevorzugen Sie die Wertsemantik auf die Referenzsemantik
-Das Rad nicht neu erfinden, stl und boost verwenden
-Verwenden Sie das Pimpl-Idiom, um private auszublenden und / oder eine Compiler-Firewall bereitzustellen
quelle
;
Zumindest in einigen Versionen von VC wird kein Finale am Ende einer Klassenerklärung angezeigt.quelle