Zum Inhalt springen

Suchen...

C++ Qualität by Design

C++ ist 45 Jahre alt und trotzdem tief im autonomen Fahren, in Medizingeräten und KI-Bibliotheken verankert. Was die Core Guidelines damit zu tun haben.

9 Min. Lesezeit
Cover für C++ Qualität by Design

Die C++ Core Guidelines sind ein community-getragenes Regelwerk für sichere, moderne C++-Entwicklung, herausgegeben vom Spracherfinder Bjarne Stroustrup und Herb Sutter. Ihr Kern: klare Ownership-Semantik, also ob Code eine Ressource besitzt oder nur leiht. Falsche Ownership ist laut Transkript die häufigste Ursache für abstürzende Programme.

Das Wichtigste in Kürze

  • Ownership-Semantik ist die häufigste Fehlerquelle in C++: ein Pointer allein zeigt nicht, ob Besitz oder nur eine Leihe übertragen wird, was zu Abstürzen und Ressourcenlecks führt.
  • C++ erhält seit dem Standard von 2011 alle drei Jahre eine neue Version, wobei die Standards von 2011 und 2020 die Programmierweise grundlegend verändert haben.
  • In sicherheitskritischen und regulierten Bereichen wie Medizintechnik oder Automobil lohnt sich Refactoring auf neue Sprachversionen meist nicht, weil jede Änderung den Zertifizierungsprozess neu auslöst.
  • Die C++ Core Guidelines werden von der Community getragen, nicht von einem Standardisierungskomitee, und erklären per Design, wie man Besitz, Interfaces und Ressourcen korrekt modelliert.
  • KI-Bibliotheken wie die hinter Python-Frameworks haben zwar einen Python-Wrapper, sind aber in C++ implementiert, was zeigt, dass die Sprache im Kern moderner Hochleistungssysteme bleibt.

C++ ist 45 Jahre alt und immer noch hochaktiv

C++ ist eine der ältesten Hochsprachen, die heute noch im breiten Einsatz sind, und es wird weiterhin aktiv weiterentwickelt. Die Sprache ist rund 45 Jahre alt. Bjarne Stroustrup, der Erfinder von C++, sprach vor einigen Jahren auf einer Konferenz in Berlin von 40 Jahren, seitdem sind weitere Jahre vergangen.

Wer C++ für einen alten Hut hält, liegt falsch. Die Sprache steckt in Steuergeräten, in Komponenten und in Bibliotheken. Sie wird über eine sehr aktive Community vorangetrieben, die moderne Konzepte aufnimmt und in neue Standards überführt.

Das Alter bringt Altlast mit. C++ wurde für eine Hardware entworfen, die es heute nicht mehr gibt. Trotzdem ist es eine General-Purpose-Sprache geblieben, keine, die nur in einer Nische lebt wie Fortran oder Cobol. Sie hat einen allgemeinen Anspruch und entwickelt sich fast im Gleichschritt mit Python und Java weiter, nur etwas langsamer, weil sie standardisiert ist.

Warum sich C++ alle drei Jahre verändert

Seit C++11 erscheint alle drei Jahre ein neuer Standard: 11, 14, 17, 20, 23. Zwei dieser Versionen sind so massiv, dass sie die Art und Weise ändern, wie programmiert wird.

C++ steht unter einer starken Anforderungswelle. Die Sprache wird massiv in sicherheitskritischen Systemen eingesetzt, im Embedded-Bereich und in der Automobilindustrie. Hinzu kommen Sicherheitsanforderungen aus Standards wie MISRA C++. Neue Features, neue Anforderungen aus der Industrie und ein hoher Sicherheitsanspruch treffen auf eine ohnehin komplexe Sprache.

Die Komplexität ist kein Zufall. Eine komplexe Domäne erzeugt eine komplexe Sprache. Deshalb lohnt es sich, eine Vorstellung davon zu haben, wie man C++ per Design richtig verwendet und die Wahrscheinlichkeit gering hält, sich selbst ein Bein zu stellen.

Die drei großen Sprünge in der C++-Evolution

Die Entwicklung von C++ lässt sich auf drei Stufen reduzieren. Jede markiert einen klaren Bruch in der Art zu programmieren.

StandardBedeutung
C++98Erster Standard, die Sprache wird definiert.
C++11Die Sprache wird stark revolutioniert: Lambdas, Move-Semantik, eine erste Idee von Nebenläufigkeit, neue Libraries. Ab hier spricht man von modernem C++, davor von Legacy C++.
C++20Vier Feature-Reihen heben die Sprache auf ein anderes Level: Ranges, Module, Coroutinen und Concepts.

Der 20er-Standard ist so anders, dass es dafür noch keinen etablierten Namen gibt. Für 11 und 17 passt “modern”, für 20 skaliert “postmodern” nicht. Klar ist nur: Es ist ein massiver Bruch, der die Sprache stark verändert.

Alten Code nicht aus Prinzip refactoren

Bestehenden, funktionierenden C++-Code solltest du nicht allein deshalb umschreiben, weil ein neuer Standard verfügbar ist. C++ folgt einer Meta-Regel: Ein moderner Compiler muss auch alten 98er-Code übersetzen können.

Die sinnvolle Strategie lautet, neue Technik dann einzusetzen, wenn ein Fehler gefunden wurde oder etwas Neues entsteht. Funktionierenden Altcode komplett umzubauen, bringt zwei Risiken mit sich. Du führst neue Bugs ein, und du wirfst die Erfahrung weg, die in diesem Code steckt, weil deine Kunden die alten Fehler längst gefunden haben.

In hochregulierten Umgebungen verschärft sich das. Im Medizinbereich muss ein Gerät abgenommen werden, jede Änderung kann den ganzen Prozess neu auslösen, bis hin zur klinischen Evaluation. Geändert wird oft erst, wenn der zugrunde liegende Chip nicht mehr verfügbar ist. Deshalb kaufen Firmen in diesem Bereich Chips auf Jahre hinaus ein, um Planungssicherheit zu haben.

Was die C++ Core Guidelines sind

Die C++ Core Guidelines sind eine Sammlung von Best Practices, die beschreiben, wie man per Design guten, modernen C++-Code schreibt. Sie decken Interfaces, Funktionen, Klassen, Vererbung und weitere Bereiche ab, in rund 14 Sektionen mit vielen einzelnen Regeln.

Anders als Standards wie MISRA C++ oder Autosar C++14 werden die Core Guidelines von keinem Standardisierungskomitee und keiner Firma getragen, sondern von der Community. Die beiden Haupteditoren sind Bjarne Stroustrup, der Erfinder von C++, und Herb Sutter, der Vorsitzende des Standardisierungskomitees.

Die etablierten Sicherheitsstandards haben ein Problem mit dem Alter. MISRA C++ stammt von 2008 und schult zum Teil Technik, die heute so nicht mehr gemacht wird, auch wenn er gerade aktualisiert wird. Wer den Code, den er vor zwei Jahren geschrieben hat, heute nicht ändern möchte, hat in dieser Zeit nichts dazugelernt. Eine Generation Software dauert in dieser Sicht zwei bis drei Jahre.

Die Core Guidelines sind in ihrer Originalform schwer zu lesen. Sie ähneln dem Buch “Design Patterns” der Gang of Four von 1994: inhaltlich wichtig, aber monoton, weil jede Regel mechanisch durchgearbeitet wird. Manche Regeln sind redundant, manche schlecht erklärt, manche sehr wichtig, manche weniger. Sie brauchen eine lesbare Hülle drumherum.

Ownership versus Leihen: die wichtigste Regel

Die mit Abstand wichtigste Designregel betrifft den Unterschied zwischen Besitz und Leihen einer Ressource. Wenn ein C++-Programm abstürzt, hat das fast immer damit zu tun, dass diese beiden Konzepte vermischt wurden.

Eine Ressource kann Speicher sein, ein Mutex, ein File-Handle, ein Socket oder ein Gerät, das hochgefahren und wieder heruntergefahren wird. Sobald du Verantwortung für etwas übernimmst, musst du klären: Bist du Besitzer, oder leihst du es nur?

Der Designfehler im klassischen C und C++ liegt im Pointer. Ein Pointer modelliert beides, Besitz und Leihen, und man sieht ihm nicht an, was gemeint ist. Wer einen nackten Pointer in der Hand hält, weiß nicht, ob er die Ressource wieder freigeben muss oder nicht.

Wenn ich meinem Sohn meinen Autoschlüssel zuwerfe, ist er dann Besitzer, oder habe ich ihm das Auto nur geliehen? Ist er Besitzer, darf er es gegen den Baum fahren. Habe ich es ihm nur geliehen, bekommt er ein Problem mit mir.

Rainer Grimm

Modernes C++ löst das über klare Übergabearten an Funktionen: per value, per Referenz, per Pointer, per unique pointer, per shared pointer. Jede dieser Varianten modelliert genau, ob Besitz übertragen oder etwas geliehen wird. Auf Dokumentation zu programmieren reicht nicht. Die Verbindlichkeit muss im Typ stehen.

Je weniger du programmierst, desto besser werden deine Programme

Eine zentrale Faustregel lautet, dem Compiler so viel Freiheit wie möglich zu lassen. Der Compiler ist sehr clever, und je mehr man ihm vorschreibt, desto schlechter wird das Ergebnis, sowohl bei Fehlern als auch bei Performance.

Ein Beispiel sind die sechs speziellen Member-Funktionen, die der Compiler bei Bedarf selbst erzeugt: Default-Konstruktor, Destruktor, Copy- und Move-Konstruktor sowie Copy- und Move-Zuweisungsoperator. Sobald du an einer Stelle etwas falsch designst, stellt der Compiler diese Erzeugung ein, weil er paranoid ist. Dann musst du alle sechs selbst schreiben, mit entsprechendem Fehlerpotenzial.

Performance profitiert ebenfalls von Zurückhaltung. Optimierer arbeiten am besten mit lokalem Code, weil sie dann alle Informationen haben und am meisten daraus schließen können. Lokaler Code ist eine simple, wirksame Regel.

Wartbarkeit entsteht durch fremden Code, nicht durch eigenen

Wartbaren C++-Code schreibst du, indem du das Rad nicht selbst erfindest. Eine Core-Guideline empfiehlt, die Standardbibliothek zu verwenden oder, wo das nicht reicht, sehr gut etablierte Libraries wie Boost.

Der Vorteil ist klar. Wer auf etablierte Bibliotheken setzt, ist nicht mehr Maintainer dieses Codes. Die Abstraktion hat jemand anderes gebaut, du bist nur noch Anwender. Die Wartbarkeit ist damit von außen gegeben.

Beim Tooling hat C++ historisch eine Schwäche. Mit C++20 kommen Module hinzu. In C++23 genügt import std, um die gesamte Standardbibliothek in einem Zug einzubinden, ohne sich mit Headern zu beschäftigen.

Macros must go

Ein breiter Konsens in der C++-Community lässt sich auf eine Formel bringen: Macros must go. Mit C++20 und 23 lassen sich Makros vollständig loswerden, weil es für sie keinen Grund mehr gibt.

Die Ersatzmittel stehen bereit. constexpr-Funktionen laufen zur Compile-Zeit, bedingte Kompilierung gelingt ebenfalls über constexpr, und mit Modulen verschwindet die Notwendigkeit für Include-Dateien. import std ist deutlich schneller und ignoriert Makros vollständig.

Wie C++ mit Fehlern umgeht

Bei der Fehlerbehandlung hat C++ eine Schwäche, weil Exceptions Kosten verursachen, vor allem im Moment des Werfens. Solange nichts passiert, kosten sie nichts, was im Normalfall genau richtig ist.

C++23 führt mit std::expected einen Datentyp aus der funktionalen Welt ein. Er kann einen erwarteten und einen unerwarteten Wert tragen. Du bedienst beide Kanäle, und der Fehler wird durch die Aufrufkette propagiert. Tritt ein Fehler auf, stoppt die Verarbeitung, und du bekommst die Fehlermeldung.

Funktionale Ideen prägen modernes C++

Moderne Programmiersprachen nehmen verstärkt Ideen aus der funktionalen Programmierung auf, und C++ tut das besonders stark. Der Hintergrund: Programmieren ist für unsere begrenzten Köpfe zu komplex, deshalb fließen formale Konzepte aus der Mathematik in die Sprachen ein.

Besonders bei Nebenläufigkeit und Threading versucht man, Systeme formaler zu machen, damit sich im Zweifel beweisen lässt, dass sie das tun, was sie sollen. Die einflussreichste Sprache der vergangenen Jahre ist hier Haskell. Kaum jemand setzt Haskell produktiv ein, doch seine Ideen wirken stark in andere Sprachen hinein.

In C++20 zeigt sich das an Ranges, der zweiten Implementierung der Standard Template Library, die funktionale Ideen wie Funktionskomposition und Lazy Evaluation mitbringt. Auch Concepts wurde maßgeblich von Personen aus der funktionalen Welt beeinflusst.

Abstraktion für null Kosten als Alleinstellungsmerkmal

C++ verfolgt einen hohen Anspruch: Abstraktion ohne zusätzliche Kosten. Die Meta-Regel lautet “don’t pay for anything you don’t use”. Genau hier unterscheidet sich C++ von anderen Sprachen.

Java und Python bieten Abstraktion, aber nicht zu Nullkosten. C bietet keine zusätzlichen Kosten, dafür auch keine Abstraktion. C++ will beides, und Rust geht in eine ähnliche Richtung.

Ein kommendes Beispiel ist Compile Time Reflection, die C++ mit hoher Wahrscheinlichkeit in C++26 bekommt. Damit lässt sich zur Compile-Zeit abfragen, welcher Typ vorliegt und was er unterstützt. Das ist nützlich fürs Testen und für Serialisierung. Anders als bei Runtime Reflection in Python oder Java soll das ohne Laufzeitkosten geschehen, was die Umsetzung deutlich komplizierter macht.

Warum C++ ein Überlebenskriterium für ganze Branchen ist

Die deutsche Automobilindustrie und ihre Zulieferer hängen massiv von C++ ab. Autonomes Fahren und das Auswerten von Videodaten werden in C++ umgesetzt.

Auch im Umfeld von KI führt der Weg zu C++. Es gibt komfortable Python-Wrapper, doch die eigentliche Implementierung der Libraries steckt darunter in C++. Irgendjemand muss diese Bibliotheken schreiben, und das geschieht selten in Python.

Zwanzig Jahre alte Software ist dabei Fluch und Segen zugleich. Segen, weil man so lange am Markt ist. Fluch, weil man die Last dauerhaft mit sich trägt. Genau darin liegt die Anforderung an eine Sprache, die alt ist und trotzdem modern bleiben will.

Diese Seite teilen

Ähnliche Beiträge