Hinweis
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, sich anzumelden oder das Verzeichnis zu wechseln.
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, das Verzeichnis zu wechseln.
C++ ist zu einer der am häufigsten verwendeten Programmiersprachen der Welt geworden. Gut geschriebene C++-Programme sind schnell und effizient. Die Sprache ist flexibler als andere Sprachen: Sie kann auf den höchsten Abstraktionsebenen und auf der Ebene des Siliziums funktionieren.
C++ bietet hochgradig optimierte Standardbibliotheken. Es ermöglicht den Zugriff auf Hardwarefunktionen auf niedriger Ebene, um die Geschwindigkeit zu maximieren und die Arbeitsspeicheranforderungen zu minimieren. C++ kann fast jede Art von Programm erstellen: Spiele, Gerätetreiber, High-Performance Computing, Cloud, Desktop, eingebettete und mobile Apps und vieles mehr. Sogar Bibliotheken und Compiler für andere Programmiersprachen werden in C++ geschrieben.
Eine der ursprünglichen Anforderungen für C++ war Abwärtskompatibilität mit der Programmiersprache C. Daher ermöglicht C++ die Programmierung im C-Stil mit unformatierten Zeigern, Arrays, null-beendeten Zeichenfolgen und anderen Features. Sie können eine hohe Leistung bieten, aber auch Fehler und zusätzliche Komplexität verursachen.
Die Entwicklung von C++ betont Features, die die Notwendigkeit der Verwendung von C-Stil-Idioms erheblich reduzieren. Die alten C-Programmiereinrichtungen sind immer noch vorhanden, wenn Sie sie benötigen. Im modernen C++-Code sollten Sie sie jedoch weniger und weniger benötigen. Moderner C++-Code ist einfacher, sicherer, eleganter und immer noch so schnell wie gewohnt.
In den folgenden Abschnitte finden Sie eine Übersicht über die Features in modernem C++. Sofern nicht anders angegeben, sind die hier aufgeführten Features in C++ 11 und höher verfügbar. Im Microsoft-C++-Compiler können Sie in der Compileroption /std festlegen, welche Version des Standards für Ihr Projekt verwendet werden soll.
Ressourcen und intelligente Zeiger
Eine der häufigsten Fehlerkategorien bei der C-Programmierung ist der Arbeitsspeicherverlust. Speicherlecks werden oft dadurch verursacht, dass delete für Speicher, der mit new zugewiesen wurde, nicht aufgerufen wird. Modernes C++ betont das Prinzip des Ressourcenerwerbs bei der Initialisierung (RAII).
Die Idee ist einfach: Ressourcen, z. B. Heap-Speicher, Dateihandles und Sockets, sollten einem Objekt gehören . Dieses Objekt erstellt oder empfängt die neu allokierte Ressource in seinem Konstruktor und löscht sie in seinem Destruktor. Das RAII-Prinzip garantiert, dass alle Ressourcen ordnungsgemäß zum Betriebssystem zurückgegeben werden, wenn das Besitzerobjekt den Gültigkeitsbereich verlässt.
Um die einfache Einführung von RAII-Prinzipien zu unterstützen, bietet die C++-Standardbibliothek drei intelligente Zeigertypen:
Ein Smart Pointer verwaltet die Zuweisung und Freigabe des Speichers, den er besitzt. Im folgenden Beispiel sehen Sie eine Klasse mit einem Arrayelement, die dem Heap im Aufruf von make_unique() zugeordnet wird. Die unique_ptr-Klasse kapselt die Aufrufe an new und delete. Wenn ein widget Objekt außerhalb des Gültigkeitsbereichs verbleibt, wird der unique_ptr Destruktor aufgerufen und gibt den Speicher frei, der für das Array zugewiesen wurde.
#include <memory>
class widget
{
private:
std::unique_ptr<int[]> data;
public:
widget(const int size) { data = std::make_unique<int[]>(size); }
void do_something() {}
};
void functionUsingWidget() {
widget w(1000000); // lifetime automatically tied to enclosing scope
// constructs w, including the w.data gadget member
// ...
w.do_something();
// ...
} // automatic destruction and deallocation for w and w.data
Verwenden Sie nach Möglichkeit einen intelligenten Zeiger zum Verwalten des Heapspeichers. Wenn Sie die Operatoren new und delete explizit verwenden müssen, befolgen Sie das RAII-Prinzip. Weitere Informationen finden Sie unter Objektlebenszeit und Ressourcenverwaltung (RAII).
std::string und std::string_view
C-Zeichenfolgen sind eine weitere häufige Fehlerquelle. Mithilfe von std::string und std::wstring können Sie praktisch alle Fehler beseitigen, die mit C-Zeichenfolgen in Zusammenhang stehen. Außerdem profitieren Sie von Memberfunktionen zum Suchen, Anhängen, Voranstellen und so weiter. Beide sind für die Geschwindigkeit optimiert. Wenn Sie eine Zeichenfolge an eine Funktion übergeben, die nur schreibgeschützten Zugriff zulässt, können Sie in C++ 17 std::string_view verwenden, um die Leistung weiter zu verbessern.
std::vector und andere Container der C++-Standardbibliothek
Sämtliche Container der Standardbibliothek befolgen das RAII-Prinzip. Sie stellen Iteratoren für den sicheren Durchlauf von Elementen bereit. Sie sind hochoptimiert für die Leistung und werden gründlich auf Korrektheit getestet. Wenn Sie diese Container verwenden, reduzieren Sie die Wahrscheinlichkeit, dass Fehler oder Ineffizienzen in benutzerdefinierten Datenstrukturen eingeführt werden. Verwenden Sie vector anstelle von unformatierten Arrays als sequenziellen Container in C++.
vector<string> apples;
apples.push_back("Granny Smith");
Verwenden Sie map (nicht unordered_map) als assoziativen Standardcontainer. Verwenden Sie set, multimap und multiset für degenerierte Fälle und Mehrfachfälle.
map<string, string> apple_color;
// ...
apple_color["Granny Smith"] = "Green";
Wenn Sie Leistungsoptimierung benötigen, sollten Sie Folgendes verwenden:
- Unsortierte assoziative Container wie
unordered_map– Diese Container haben einen geringeren Overhead pro Element und ermöglichen Nachschlagen in konstanter Zeit, können jedoch schwieriger korrekt und effizient zu verwenden sein. - Sortiert
vector. Weitere Informationen finden Sie unter Algorithmen.
Verwenden Sie keine Arrays im C-Stil. Verwenden Sie für ältere APIs, die direkten Zugriff auf die Daten benötigen, stattdessen Zugriffsmethoden wie f(vec.data(), vec.size());. Weitere Informationen zu Containern finden Sie unter C++-Standardbibliothekcontainer.
Algorithmen der Standardbibliothek
Bevor Sie davon ausgehen, dass Sie einen benutzerdefinierten Algorithmus für Ihr Programm schreiben müssen, überprüfen Sie die C++- Standardbibliotheksalgorithmen. Die Standardbibliothek enthält einen stetig wachsenden Bestand an Algorithmen für viele gängige Vorgänge wie das Suchen, Sortieren, Filtern und Randomisieren. Die mathematische Bibliothek ist umfangreich. In C++17 und höher werden parallele Versionen vieler Algorithmen bereitgestellt.
Im Folgenden sind einige wichtige Beispiele aufgeführt:
-
for_each: der Standard-Traversalalgorithmus zusammen mit den bereichsbasiertenfor-Schleifen. -
transform: für nicht direkte Änderung von Containerelementen. -
find_if: der Standardmäßige Suchalgorithmus. -
sort,lower_boundund weitere Standardsortier- und -suchalgorithmen
Verwenden Sie beim Schreiben einer Vergleichsfunktion den strikten <-Operator und, wenn möglich, benannte Lambdaausdrücke.
auto comp = [](const widget& w1, const widget& w2)
{ return w1.weight() < w2.weight(); }
sort( v.begin(), v.end(), comp );
auto i = lower_bound( v.begin(), v.end(), widget{0}, comp );
auto anstelle von expliziten Typnamen
In C++ 11 wurde das auto-Schlüsselwort für die Verwendung in Variablen-, Funktions- und Vorlagendeklarationen eingeführt.
auto weist den Compiler an, den Typ des Objekts abzuleiten, damit Sie ihn nicht explizit eingeben müssen.
auto ist besonders nützlich, wenn der abgeleitete Typ eine geschachtelte Vorlage ist:
map<int,list<string>>::iterator i = m.begin(); // C-style
auto i = m.begin(); // modern C++
Bereichsbasierte for-Schleifen
Iterationen im C-Stil über Arrays und Container sind anfällig für Indexierungsfehler und zudem umständlich zu schreiben. Sie können bereichsbasierte for-Schleifen mit Containern der Standardbibliothek und unformatierten Arrays verwenden, um diese Fehler zu vermeiden und Ihren Code lesbarer zu gestalten. Weitere Informationen finden Sie unter Bereichsbasierte for-Anweisung.
#include <iostream>
#include <vector>
int main()
{
std::vector<int> v {1,2,3};
// C-style
for(int i = 0; i < v.size(); ++i)
{
std::cout << v[i];
}
// Modern C++:
for(auto& num : v)
{
std::cout << num;
}
}
constexpr-Ausdrücke anstelle von Makros
Makros entsprechen in C und C++ Tokens, die vom Präprozessor vor der Kompilierung verarbeitet werden. Jede Instanz eines Makrotokens wird durch den definierten Wert oder Ausdruck ersetzt, bevor die Datei kompiliert wird. Makros werden bei der C-Programmierung häufig verwendet, um konstante Werte für die Kompilierzeit zu definieren. Makros sind jedoch fehleranfällig und schwer zu debuggen. In modernem C++ sollten Sie constexpr-Variablen für Kompilierzeitkonstanten bevorzugen:
#define SIZE 10 // C-style
constexpr int size = 10; // modern C++
Einheitliche Initialisierung
In modernem C++ können Sie die Initialisierung mit geschweiften Klammern für jeden Typ verwenden. Diese Form der Initialisierung ist besonders praktisch, wenn Arrays, Vektoren oder andere Container initialisiert werden. Im folgenden Beispiel wird v2 mit drei Instanzen von S initialisiert.
v3 wird mit drei Instanzen von S initialisiert, die ihrerseits mithilfe geschweifter Klammern initialisiert werden. Der Compiler leitet den Typ jedes Elements auf Grundlage des deklarierten Typs von v3 ab.
#include <vector>
struct S
{
std::string name;
float num;
S(std::string s, float f) : name(s), num(f) {}
};
int main()
{
// C-style initialization
std::vector<S> v;
S s1("Norah", 2.7);
S s2("Frank", 3.5);
S s3("Jeri", 85.9);
v.push_back(s1);
v.push_back(s2);
v.push_back(s3);
// Modern C++:
std::vector<S> v2 {s1, s2, s3};
// or...
std::vector<S> v3{ {"Norah", 2.7}, {"Frank", 3.5}, {"Jeri", 85.9} };
}
Weitere Informationen finden Sie unter Initialisierung mit geschweiften Klammern.
Verschiebesemantik
Modernes C++ enthält eine Verschiebesemantik, sodass unnötige Arbeitsspeicherkopien vermieden werden können. In früheren Versionen der Sprache waren Kopien in bestimmten Situationen unvermeidlich. Ein Verschiebevorgang überträgt den Besitz einer Ressource von einem Objekt zum nächsten, ohne eine Kopie zu erstellen. Einige Klassen verwalten Ressourcen wie Heap-Speicher, Datei-Handles und so weiter.
Wenn Sie eine ressourcenbesitzende Klasse implementieren, können Sie einen Verschiebungskonstruktor und einen Verschiebungszuweisungsoperator definieren. Der Compiler wählt diese speziellen Member bei der Überladungsauflösung in Situationen aus, in denen keine Kopie benötigt wird. Die Containertypen der Standardbibliothek rufen den Verschiebungskonstruktor für Objekte auf, wenn dieser definiert ist. Weitere Informationen finden Sie unter Verschiebungskonstruktoren und Verschiebungszuweisungsoperatoren (C++).
Lambdaausdrücke
Bei der C-Programmierung kann eine Funktion mithilfe eines Funktionszeigers an eine andere Funktion übergeben werden. Funktionszeiger sind unpraktisch zu verwalten und nachzuvollziehen. Die Funktion, auf die sie verweisen, kann an anderer Stelle im Quellcode definiert werden, weit von dem Punkt, an dem sie aufgerufen wird. Außerdem sind sie nicht typsicher.
Modernes C++ bietet Funktionsobjekte, bei denen es sich um Klassen handelt, die den operator()-Operator überschreiben, sodass sie wie eine Funktion aufgerufen werden können. Funktionsobjekte können am einfachsten mithilfe von Inline-Lambdaausdrücken erstellt werden. Das folgende Beispiel zeigt, wie Sie einen Lambda-Ausdruck verwenden, um ein Funktionsobjekt zu übergeben, das von der find_if Funktion für jedes Element im Vektor aufgerufen wird:
std::vector<int> v {1,2,3,4,5};
int x = 2;
int y = 4;
auto result = find_if(begin(v), end(v), [=](int i) { return i > x && i < y; });
Der Lambda-Ausdruck [=](int i) { return i > x && i < y; } kann als "Funktion, die ein einzelnes Argument vom Typ int akzeptiert, gelesen werden und gibt einen booleschen Wert zurück, der angibt, ob das Argument größer als x und kleiner als ist y." Beachten Sie, dass die Variablen x und y aus dem umgebenden Kontext in der Lambda-Funktion verwendet werden können. Gibt [=] an, dass diese Variablen nach Wert erfasst werden. Mit anderen Worten, der Lambda-Ausdruck verfügt über eigene Kopien dieser Werte.
Ausnahmen
Modern C++ betont Ausnahmen, keine Fehlercodes, als beste Methode zum Melden und Behandeln von Fehlerbedingungen. Weitere Informationen finden Sie unter Modernes C++: Best Practices für Ausnahmen und Fehlerbehandlung.
std::atomic
Verwenden Sie die Struktur std::atomic der C++-Standardbibliothek und ähnliche Typen für die Kommunikation zwischen Threads.
std::variant (C++ 17)
Unions werden in der C-Programmierung häufig verwendet, um Speicher zu sparen, indem Elemente unterschiedlicher Typen denselben Speicherbereich belegen. Unions sind nicht typsicher und anfällig für Programmierfehler. C++ 17 führt die std::variant-Klasse als robustere und sicherere Alternative zu den Unions ein. Die std::visit-Funktion kann verwendet werden, um typsicher auf die Member eines variant-Typs zuzugreifen.