Was lange währt, wird endlich wahr

Modernes C++11
Kommentare

Im Herbst vergangenen Jahres wurde C++11, der Nachfolger des bisherigen Standards C++99, verabschiedet. Zahlreiche Compilerhersteller sind nun eifrig damit beschäftigt, die Neuerungen in ihre Werkzeuge zu integrieren.

Windows Developer

Der Artikel „Was lange währt, wird endlich wahr“ von Thomas Trotzki und Christian Binder ist erstmalig erschienen im Windows Developer 9.2012

C/C++ hat eine lange Geschichte und noch immer eine sehr große Zahl an Anhängern. Doch die Normierung der weit verbreiteten Sprache nimmt immer wieder eine recht lange Zeit in Anspruch. So musste auch auf C++11 zwölf Jahre gewartet werden. Zwar wurde bereits 2003 mit TR1 (auch C++ 03 genannt) ein Zwischenstand als Draft vorgelegt, an dem sich die Compilerhersteller orientieren konnten, endgültig verabschiedet wurde dieser aber nicht. Sicherlich wird aber noch einige Zeit vergehen, bis alle Features auch in jedem kommerziellen C++-Compiler Umsetzung gefunden haben werden. Gnu als Open Source hat hier die Nase vorn. Das Warten auf C++11 hat sich aber gelohnt, stehen nun doch neue Leistungsmerkmale zur Verfügung, die die Lücke zwischen C++ und Sprachen wie Java oder C# in weiten Bereichen schließen können. Die großen Highlights stellen zweifelsohne Lambdas dar, die auch für die ebenfalls normierte Standardbibliothek STL ganz neue Möglichkeiten eröffnet haben. Aber auch andere neue Schlüsselworte, wie auto, override und final finden in diesem Artikel Beachtung. Doch beginnen wir zunächst mit einigen kleineren Neuerungen.

Artikelserie
  1. Modern C++11
  2. Going Parallel with C++
  3. ALM for C++
nullptr

Zeiger sind in C/C++ ein Sprachkonstrukt der ersten Stunde. Sie verweisen auf andere Variablen, speichern deren Adresse oder eben nicht. Bislang hat der C++-Programmierer eine explizite Konstante für einen Zeiger, der auf nichts zeigt, vergeblich gesucht. nullptr löst nun den per Konvention getroffen Wert 0 oder den in vielen Compilern verwendeten #define NULL 0 ab. Neben einer besseren Lesbarkeit werden dadurch auch einige Fehlersituationen umgegangen, die nach der bisherigen Konvention durchaus Probleme gemacht haben. So ist nullptr in sämtliche Zeiger-Datentypen implizit konvertierbar, nicht aber in integrale Datentypen mit Ausnahme von bool:

char *pChar = nullptr; // OK
int * pInt = nullptr; // OK
int val = nullptr; // error  

Interessant wird dies in folgender Situation:

void f(char *);
void f (int);
f(nullptr);  
auto

C++ ist eine streng typgebundene Sprache. Daher mussten bislang Variablen auch unter Angabe des gewünschten Typs vereinbart werden. Das Problem dabei: Seit der Einführung von Templates ist der Rückgabetyp einer Funktion nicht immer so einfach zu erkennen, trotz Mechanismen wie Intellisense. Die meisten von uns werden die Situation kennen, dass ein Aufruf der gewünschten Funktion durch einen Compilerfehler quittiert wird. Dieser besagt dann in etwa, dass der Typ , >> nicht in int konvertiert werden kann. Und zumindest mir gelingt es trotz höchster Konzentration meist auf Anhieb nur , Zeichen, wieder>, so, habe> einzutippen. Es folgen dann Momente, in denen mich die Beharrlichkeit eines Compilers in leichte Erregung versetzen kann. Mit auto wird dieses Problem endlich gelöst:

auto result = 
  ThisFunctionReturnsANestedTemplate();  

auto kann für alle Variablen verwendet werden, die direkt bei ihrer Definition initialisiert werden und wird vom Compiler automatisch durch den Typ des Initializer ersetzt, bei Funktionsaufruf durch den Datentyp des Rückgabewertes – ein einfaches, aber sehr nützliches Sprachmerkmal. Die bisherige Bedeutung von auto ist nun deprecated, d. h. sollte nicht mehr genutzt werden und wird wohl irgendwann komplett entfallen.

To Collect or not Collect

Das Thema Garbage Collector beschäftigt die C++-Community immer wieder. Bei zahlreichen Veranstaltungen werden wir als Berater gefragt, wann denn endlich ein Garage Collector in C++ Einzug halten wird. Die einfache Antwort: Wohl niemals, er wird eigentlich nicht benötigt. Ursache für die Diskussionen um das Thema Garbage Collector sind zum einen die C++-verwandten Programmiersprachen Java und C#. Diese wurden mit viel Wirbel um eben jenen eingeführt, und zum anderen liefert natürlich auch das eigentliche Problem der Memory Leaks entsprechend Zündstoff. Diese waren der eigentliche Motivationsgrund für Garbage Collectors. Doch die Räder drehen sich weiter. Zwischenzeitlich hat sich vieles bezüglich der Vermeidung von Memory Leaks getan. Grenzt man das Problem genauer ein, so stellt man folgende Punkte fest:

  • Value Types vs. Reference Types: C++ als Programmiersprache arbeitet per Default mit Value Types. Auch Klassen definieren Value Types. Alles was in C++ als integraler Datentyp angelegt wird, wird bei Verlassen des Gültigkeitsbereiches automatisch bereinigt. Somit sind herkömmliche integrale Member-Variablen und Stack-Variablen nicht das Problem in C++. Garbage-Collected-Sprachen dagegen legen Instanzen von Klassen immer auf dem Heap an. Values Types gibt es, sie sind aber recht dünn gesät.
  • Destruktoren werden in C++ immer deterministisch aufgerufen, dies auch beim Verlassen des Gültigkeitsbereiches der entsprechenden Variablen. In Garbage-Collected-Sprachen ist dies nicht der Fall. In C# z. B. wird der Finalizer erst dann aufgerufen, wenn der Garbage Collector das entsprechende Objekt aufräumt.

Somit machen in C++ eigentlich nur Objekte bzw. Speicherbereiche Probleme, die explizit per new auf dem Heap allokiert und über einen Zeiger verwaltet werden. Geht dieser out of Scope bevor delete aufgerufen wurde, so entsteht das gefürchtete Memory Leak. Die Kombination der beiden eben gerade aufgeführten Sachverhalte liefert jedoch bereits die Lösung des Problems. Smart Pointer kapseln den Zeiger in einem kleinen Hüllobjekt und dieses ruft im Destrukor delete auf. Dies geschieht genau dann, wenn das Hüllobjekt seinen Gültigkeitsbereich verlässt und somit absolut deterministisch. Ergo: Wer braucht schon einen Garbage Collector? Umgekehrt betrachtet, bringt auch ein Garbage Collector Probleme mit sich: Verwaltet ein Objekt eine unmanaged Resource, so genügt es oftmals nicht, diese im Destruktor/Finalizer freizugeben. Dieser wird erst zeitverzögert durch den Garbage Collector und damit nicht deterministisch aufgerufen. Handelt es sich bei dieser unmanged Resource um eine exklusiv geöffnete Datei, steht diese für andere eine unvorhersehbare Zeit nicht zur Verfügung. Und somit haben wir ein Resource Leak – umgekehrtes Problem, aber vergleichbarer Effekt! Die Schöpfer der Garbage-Collected-Programmiersprachen haben hierfür das Dispose Pattern nachgereicht. Es sieht eine Methode vor (C#: Dispose(), Java: dispose()), die explizit aufgerufen werden muss und die unmanged Resource freigibt, bevor das Objekt seinen Gültigkeitsbereich verlässt. Um diesen Aufruf sicherzustellen, muss sehr häufig try/catch/finally bemüht werden. Zur Vereinfachung wurde in C# hierfür extra das Schlüsselwort using eingeführt. Verglichen damit sind Smart Pointer in C++ auch nicht komplizierter.

Unsere Redaktion empfiehlt:

Relevante Beiträge

Meinungen zu diesem Beitrag

X
- Gib Deinen Standort ein -
- or -