C++ ist eine von der ISO genormte Programmiersprache. Sie wurde ab 1979 von Bjarne Stroustrup bei AT&T als Erweiterung der Programmiersprache C entwickelt. C++ ermöglicht sowohl die effiziente und maschinennahe Programmierung als auch eine Programmierung auf hohem Abstraktionsniveau. Der Standard definiert auch eine Standardbibliothek, zu der verschiedene Implementierungen existieren.
Einsatzgebiete C++ wird sowohl in der Systemprogrammierung als auch in der Anwendungsprogrammierung eingesetzt und gehört in beiden Bereichen zu den verbreitetsten Programmiersprachen.
Systemprogrammierung Typische Anwendungsfelder in der Systemprogrammierung sind Betriebssysteme, eingebettete Systeme, virtuelle Maschinen, Treiber und Signalprozessoren. C++ nimmt hier oft den Platz ein, der früher ausschließlich Assemblersprachen und der Programmiersprache C vorbehalten war.
Zum Beispiel werden bei der SAP SE sowohl der ABAP Kernel als auch diverse Komponenten der HANA Datenbank in C++ entwickelt.
Anwendungsprogrammierung Bei der Anwendungsprogrammierung kommt C++ vor allem dort zum Einsatz, wo hohe Anforderungen an die Effizienz gestellt werden, um durch technische Rahmenbedingungen vorgegebene Leistungsgrenzen möglichst gut auszunutzen. Ab dem Jahr 2000 wurde C++ aus der Domäne der Anwendungsprogrammierung von den Sprachen Java und C# zurückgedrängt.
Eigenschaften Sprachdesign Den Kern der Sprache bilden nur etwa 60 Schlüsselwörter, einige wenige werden jedoch zum Teil mehrfach belegt und können in unterschiedlichen Kontexten verwendet werden (z. B. static, default).
Ähnlich wie auch in der Ursprungssprache C stellt die C++-Standardbibliothek eine umfangreiche Sammlung an Strukturen und Algorithmen zur Verfügung, die in der eigentlichen Kernsprache selbst nicht enthalten sind. Dies beinhaltet unter anderem verschiedene Datencontainer (Arrays, Vektoren, Listen, Map, Sets, …), Verbindung zum Betriebssystem (iostream, fopen, exit, …), Speicherverwaltung (Allocators, Smart Pointer), als auch Schnittstellen zur Entwicklung nebenläufiger Anwendungen.
C++ legt einen Schwerpunkt auf die Sprachmittel zur Entwicklung von Bibliotheken. Dadurch favorisiert es verallgemeinerte Mechanismen für typische Problemstellungen und besitzt kaum in die Sprache integrierte Einzellösungen.
Eine der Stärken von C++ ist die Kombinierbarkeit von effizienter, maschinennaher Programmierung mit mächtigen Sprachmitteln, die einfache bis komplexe Implementierungsdetails zusammenfassen und weitgehend hinter abstrakten Befehlsfolgen verbergen. Dabei kommt vor allem die Template-Metaprogrammierung zum Zuge: Eine Technik, die eine nahezu kompromisslose Verbindung von Effizienz und Abstraktion erlaubt.
Einige Design-Entscheidungen werden allerdings auch häufig kritisiert:
Ressourcenverwaltung C++ hat keine Garbage Collection. Bis Version 20 gab es minimale Funktionalitäten für eine Garbage-Collection. Es gibt Bestrebungen, eine funktionale Garbage-Collection durch Bibliotheken oder durch Aufnahme in den Sprachstandard zu ermöglichen.Transparent Programmer-Directed Garbage Collection for C++ (PDF; 156 kB). Siehe auch Boehm-Speicherbereinigung. Mit Version 23 wurde die im Code implementierte Garbage Collection vollständig entfernt.
Es ist möglich, die Speicherverwaltung von C (malloc/realloc/free) zu verwenden; zur Implementierung von Low-Level-Funktionen in Bibliotheken wie der C++-Standardbibliothek ist dies zum Zugriff auf C Bibliotheksfunktionen notwendig. In allgemeinem C++-Code wird hiervon jedoch dringend abgeraten.Bjarne Stroustrup: C++-FAQ über Speicherlecks (englisch); abgerufen am 3. Mai 2013. Stattdessen ist es dort üblich, die Speicherverwaltung von der C++-Standardbibliothek übernehmen zu lassen, indem man die angebotenen Containerklassen verwendet.
Andere Ressourcen, z. B. Dateihandles oder Netzwerksockets werden in C++ üblicherweise in eigenen Klassen mit dem Prinzip RAII verwaltet, um das automatische Aufräumen nach der Verwendung sicherzustellen. Dies ist konzeptionell in anderen Programmiersprachen oft mit der Implementierung spezieller Basisklassen/Schnittstellen gestaltet – wie z. B. „AutoCloseable“ in Java oder „IDisposable“ in C#. In anderen Programmiersprachen benötigen diese nicht verwalteten Ressourcen oft ein spezielles Handling (z. B. „using“-Schlüsselwort in C#), um die Lebenszeit dieser Objekte ausnahmesicher in Verbindung mit dem Garbage Collector zu verwalten. Die RAII-basierten Implementierungen in C++ sind konzeptbedingt nicht anfällig bezüglich dieser Probleme, da Speicher in C++ keinen Sonderstatus als Ressource hat. Eine korrekte Implementierung der Objektlebenszeit garantiert hier die Freigabe aller Ressourcen.
Für Verweise auf Objekte werden üblicherweise Referenzen verwendet, solange der Verweis das Objekt nicht besitzen soll – z. B. als Parameter bei Funktionsaufrufen. Um den Besitz von dynamisch allokierten Objekten zu verwalten, werden üblicherweise Smart Pointer eingesetzt, welche die Destruktion und Speicherfreigabe übernehmen. Die Standardbibliothek unterscheidet hier strikt den Besitz. Während beim unique_ptr nur dieser Zeiger den Speicher besitzt, kann der Besitz des Speichers beim shared_ptr zwischen mehreren Zeigerinstanzen geteilt werden. Technisch realisiert ist dies beim shared_ptr meist mit Reference counting.std::shared_ptr auf cppreference.com, Abschnitt Hinweise zur Umsetzung; abgerufen am 5. Juni 2020
Unvollständige Objektorientierung Sichtbarkeit privater Elemente In C++ gehören private Eigenschaften (Variablen und Methoden) normalerweise mit zur Schnittstelle, die in der Header-Datei veröffentlicht ist. Dadurch entstehen zur Compilezeit und zur Laufzeit Abhängigkeiten der Objekte zu den Stellen, die sie verwenden.
Diese Abhängigkeiten können durch bestimmte Konstruktionen, wie dem pimpl-Idiom (), vermieden werden. Dabei werden die privaten Felder der Klasse (example_class) in eine private, vorwärts-deklarierte Hilfsklasse verschoben, und ein Zeiger auf ein Objekt dieser Hilfsklasse (example_class::impl * impl_ptr) bleibt in der eigentlichen Klasse. Die Definition der implementierenden Klasse findet bei der Implementierung der öffentlichen Klasse statt und ist damit für den Verwender der Klasse (der nur die Header-Datei kennt) unsichtbar. Dadurch, dass die Hilfsklasse nur durch einen Zeiger referenziert wird, bleiben alle Quelltextänderungen an privaten Feldern transparent und die Binärkompatibilität wird erhalten.Das d-Zeiger-Idiom. auf heise Developer.
Unvollständige Kapselung In C++ sind die Speicherbereiche der einzelnen Objekte zur Laufzeit nicht vor (absichtlichen oder versehentlichen) gegenseitigen Änderungen geschützt.
Undefiniertes Verhalten Das Verhalten von einigen Sprachkonstrukten ist nicht definiert. Dies bedeutet, dass der Standard weder vorgibt noch empfiehlt, was in einem solchen Fall passiert. Die Auswirkungen reichen von Implementierungsabhängigkeit (d. h. je nach Zielrechner und Compiler kann sich das Konstrukt unterschiedlich verhalten) über unsinnige Ergebnisse oder Programmabstürze bis hin zu Sicherheitslücken. Einige dieser Freiheiten des Compilers lassen zusätzliche Optimierungen des Codes zu.
Es kommt zu unterschiedlichem Verhalten bei verschiedenen Compilern verschiedenen Compiler-Versionen verschiedener Architektur (ARM, x86, x64) verschiedenen Optimierungseinstellungen (Debug, Release, Optimierung) ausgewählter Befehlssatz, Aufrufkonventionen, u. v. a. m. Quellcode mit Codepassagen mit undefiniertem Verhalten kann nach der Kompilierung unerwartetes und absurd erscheinenden Verhalten zeigen. So werden zu spät durchgeführte Überprüfungen wegoptimiert oder Schleifen, die auf einen ungültigen Index eines Arrays zugreifen, durch leere Endlosschleifen ersetzt., abgerufen am 20. August 2014.Scott Meyers: Effektiv C++ Programmieren. Addison-Wesley, S. 22/23, 43/44 und 46, nach Google-Books, abgerufen am 20. August 2014.
Wichtig für das Verständnis von undefiniertem Verhalten ist insbesondere, dass niemals nur eine einzelne Operation ungültig ist, sondern das gesamte Programm ungültig wird und kein wohlgeformtes C++ mehr darstellt. Der Grund ist, dass manche Arten von „undefiniertem Verhalten“ Auswirkungen auf ganz andere, auch in sich korrekte, Programmteile haben und deren Verhalten beeinflussen können, beispielsweise bei Pufferüberläufen oder der unbeabsichtigten Änderung von Prozessor-Flags, die durch eine ungültige arithmetische Operation verursacht wurde und die nachfolgenden Berechnungen beeinflussen kann.
Beispiele für undefiniertes Verhalten: Überlauf von vorzeichenbehafteten Ganzzahlen (auch z. B. bei Umwandlung von unsigned int nach int) Nullzeiger-Dereferenzierungen Arrayzugriffe mit ungültigem Index Schiebeoperationen mit einer Schiebeweite, die negativ oder größergleich der Zahl der Bits des zu schiebenden Typs ist Division durch null mit integralen Datentypen Weglassen des return-Statements in Funktionen mit Rückgabewert (die Hauptfunktion main bildet die einzige Ausnahme) Ein Nebeneffekt ändert eine Variable, die mehrmals in dem Ausdruck (v[i] = i++;) oder in der Argumentliste (f(i, i++);) vorkommt (die Auswertungsreihenfolge von Teilausdrücken und Funktionsargumenten ist nicht festgelegt)
Einerseits ist das hieraus resultierende nichtdeterministische Laufzeitverhalten, insbesondere bei kleinen Änderungen der Plattform, mindestens als Risiko, in der Praxis oft aber als klarer Nachteil einzustufen. Andererseits werden hierdurch schnellere Programme ermöglicht, da Gültigkeitsüberprüfungen weggelassen werden können und der Compiler zudem oft Programmteile stärker optimieren kann, indem er Randfälle als per Definition ausgeschlossen ignoriert.
Ein oft nicht wahrgenommener Vorteil ist darüber hinaus, dass dadurch, dass undefiniertes Verhalten praktisch nur in äußerst fragwürdigen Konstrukten auftritt, die aber nicht zwingend während des Kompilierens feststellbar sind, unsemantischer oder anderweitig suboptimaler Code gewissermaßen verboten wird.
Beispielsweise besteht eine illegale Art zu prüfen, ob die Summe zweier positiver Ganzzahlen und vom Typ ‚int‘ verlustfrei wieder in einem ‚int‘ abgebildet werden kann, daraus, zu schauen, ob ihre Summe größer 0 ist (bei Überlauf entsteht auf den meisten Computern durch die Zweierkomplement-Arithmetik eine negative Zahl). Eine derartige Überprüfung ist allerdings aus mathematischer Sicht nicht besonders sinnvoll. Eine bessere (semantischere) Herangehensweise ist hier, die eigentliche Frage, ob , wobei die größte in einem ‚int‘ darstellbare Zahl ist, nach der mathematisch validen Umformung zu zu verwenden.Myths and Missconceptions about C++, Abschnitt Undefined Behavior, abgerufen am 20. Februar 2015 (englisch).
Kompatibilität mit C Um an die Verbreitung der Programmiersprache C anzuknüpfen, wurde C++ als Erweiterung von C gemäß dem damaligen Stand von 1990 (ISO/IEC 9899:1990, auch kurz C90 genannt) entworfen.
Die Kompatibilität mit C zwingt C++ zur Fortführung einiger dadurch übernommener Nachteile. Dazu zählt die teilweise schwer verständliche C-Syntax, der als überholt geltende Präprozessor sowie verschiedene von der jeweiligen Plattform abhängige Details der Sprache, die die Portierung von C++-Programmen zwischen unterschiedlichen Rechnertypen, Betriebssystemen und Compilern erschweren.
Einige C-Sprachkonstrukte haben in C++ eine leicht abgewandelte Bedeutung oder Syntax, so dass manche C-Programme erst angepasst werden müssen, um sich als C++-Programm übersetzen zu lassen. Weitere Änderungen an C fanden in den Jahren 1999 (ISO/IEC 9899:1999, aka C99) und 2011 (ISO/IEC 9899:2011, aka C11) also nach der ersten Normung von C++ statt, so dass dort eingeflossene Änderungen nicht in C++98 berücksichtigt werden konnten. In die C++-Revision von 2011 wurde ein Teil der Neuerungen von C99 übernommen; auf der anderen Seite wurden dem C-Standard neue Features hinzugefügt, die auch mit C++11 nicht kompatibel sind.
Sprachmerkmale im Detail