zurück zum Artikel

C++11 – auch ein Stimmungsbild

Marc Mutz

Auch wenn C++ nicht gerade als beliebt unter Entwicklern gelten darf: die Sprache wird sie noch eine Weile begleiten – vor allem, weil der Bedarf an hardwarenahen Sprachen wie C++ sich noch erhöhen dürfte. Jetzt ist die Sprache in einer neuen Version erschienen.

Auch wenn C++ nicht gerade als beliebt unter Entwicklern gelten darf: die Sprache wird sie noch eine Weile begleiten – vor allem, weil der Bedarf an hardwarenahen Sprachen wie C++ sich noch erhöhen dürfte. Jetzt ist die Sprache in einer neuen Version erschienen.

Die Sprachen C und C++ gelten als komplex, ja geradezu gefährlich. Eine informelle Meinungsumfrage unter Kollegen des Autors (alles Leute, die täglich mit C++ arbeiten) zeigt dann auch, dass sie keineswegs nur geliebt werden. Die Kommentare reichen von: "Manchmal mag ich ihre Schönheit und Einfachheit. Aber dann schäme ich mich (und erinnere mich all der Stunden, die ich mit Fehlersuche zugebracht habe)" bis zu: "Ich würde C++ hassen, wenn es Qt nicht gäbe". Ein weiterer Kollege liefert gleich eine Aufzählung an Punkten, als hätte er seinen Ärger über die Zeit angespart: C++ sei übermäßig komplex, man brauche Jahre, um es zu meistern. Klobig sei es, nicht agil und nicht modern. Und normalerweise unsicher. Die Stimmung ist demnach relativ negativ.

Nach Meinung des Autors sind die Sprachen der (direkten) C-Familie (C/C++ und D) immer noch die einzigen, die sich für die Systemprogrammierung eignen. Nur sie bringen die Voraussetzungen (zum Beispiel Hardwarenähe) mit, Rechner bis an ihre Leistungsgrenze auszureizen. Ulrich Drepper sprach das in einem Vortrag auf einem früheren LinuxTag am Beispiel von NUMA-Architekturen (Non-Uniform Memory Access) an: Im Gegensatz zu Fortran, C und C++, so der GLIBC-Maintainer, seien die zur Ausreizung der Möglichkeiten moderner NUMA-Systeme nötigen Programmierschnittstellen zur Speicher- und Prozessaffinität in anderen Sprachen noch nicht einmal vorhanden.

Herb Sutter schlug in dieselbe Kerbe, als er auf der diesjährigen "C++ And Beyond"-Konferenz in seiner Keynote "Why C++ [1]" das Ende des verlorenen Jahrzehnts für C++ und die Rückbesinnung auf Performance, und damit auf C++, auch und vor allem bei seinem Arbeitgeber Microsoft, herausstellte.

Auch wenn die Aussagen und die damit einhergehenden Implikationen mit einer Portion Skepsis zu behandeln sind, zeigen sie beispielhaft eine weitverbreitete Ansicht: C und C++ sind Sprachen für die Systemprogrammierung, Java, C# und Konsorten sind nur für die Anwendungsentwicklung geeignet. Vertreter beider Lager stellen zwar gerne heraus, dass ihre Sprache sehr wohl in der Domäne der anderen zu überzeugen vermag, doch ist ein Anwendungsentwicklerteam gut beraten, C++ nur zu wählen, wenn das notwendig ist, um die Hardware auszureizen. Und umgekehrt.

Gerade deshalb haben jedoch C++ und die "sauberere C++-Alternative" D gute Chancen, verloren gegangenes Terrain (zurück) zu erobern. Die aktuelle Diskrepanz zwischen dem Fortschritt in der Parallelität der Hardware auf der einen Seite und der vergleichsweisen Rückständigkeit der Programmiermodelle auf Softwareseite zwingen (System- und Anwendungs-)Entwickler im Moment dazu, sich mit der Hardware auf einer Ebene zu beschäftigen, wie das seit den 1980er-Jahren nicht mehr der Fall war.

Damals zählte man Taktzyklen von Assembler-Befehlen, heute zählt man Bytes in Cachelines, um False Sharing [2] zu verhindern, oder der Entwickler hantiert mit (etwa SSE) Intrinsics (Streaming SIMD Extensions) herum, weil die Compiler immer noch nicht selbst optimalen SIMD-Code erzeugen können. Beides sind Gebiete, in denen (nur) C/C++ und D punkten können; anderen Sprachen fehlt die Nähe zur Hardware. Insofern ist jetzt eine aufregende Zeit – insbesondere für C++. Auf der einen Seite wird mit C und C++ schon seit Dekaden Multithreading praktiziert, auch wenn erst das jetzt veröffentlichte C++11 ein dafür geeignetes Speichermodell enthält. Aus der Vielzahl neuer Techniken seien nur OpenMP, Cilk++, RapidMind, Threading Building Blocks (TBB), CUDA, OpenCL und Ct genannt. Außer OpenCL sind diese Tools nur mit C/C++ kompatibel. Der scheinbar ewige Vorsprung von Java und C# bei den Trendwerkzeugen scheint zumindest in einem Teilbereich zum ersten Mal seit etwas mehr als zehn Jahren gebrochen.

Auf der anderen Seite ergeben sich durch die Template-Metaprogrammierung in C++ und D Mittel, numerischen Code auf einer Ebene zu (be)schreiben, für die in anderen Sprachen die Sprache selbst zu erweitern wäre, und dem Compiler durch geeignete Metaprogrammierung den effizienten Umgang mit Vektorerweiterungen in den heutigen Prozessoren beizubringen. Die Programmierschnittstellen, so sich jemand daran setzte, sie zu realisieren (dem Autor ist kein solches Projekt bekannt), hätten eine ganz andere Qualität und Stärke des Ausdrucks als die heute gängigen Bibliotheken, in denen nur solche Operationen beschleunigt möglich sind, für die die Bibliothek (zumeist) händisch optimierte explizite Routinen bereitstellt. Dass es prinzipiell viel generischer geht, hat Todd Veldhuizen mit Blitz++ vor 16 Jahren erfolgreich demonstriert [3] (ohne allerdings Autoparallelisierung anzustreben).

Da es dem neuen C++-Standard gelingt, die Sprache wesentlich anwenderfreundlicher zu gestalten, kann der Einsatz von C++ wieder merklich zunehmen. Viele der neuen Sprachfunktionen in C++11 sind direkt der Sprach-Usability gewidmet (aus der Vielzahl sei nur auto genannt oder die Erlaubnis, vector<pair<U,V>> anstelle von vector<pair<U,V> > zu schreiben). Auch bind(), Lambdas und shared_ptr/unique_ptr ersetzen faktisch besonders benutzerunfreundliche Komponenten der 98er-STL (bind1st/bind2nd beziehungsweise auto_ptr).

In dieser Aufzählung sollten sich ursprünglich auch Konzepte befinden, auch eingeschränkte Templates (engl.: constrained templates) genannt. Die Mitglieder des Standardisierungskomittees entschieden sich jedoch 2009, nach einem Artikel [4] Bjarne Stroustrups, seines Zeichens Erfinder von C++, Konzepte wieder aus dem Standard zu entfernen und erst in der nächsten Version der Sprache die Arbeit daran wieder aufzunehmen.

Nach den negativen Erfahrungen mit früheren Entscheidungen, die ihren Weg ohne die notwendige Erfahrung aus der Praxis in den Standard gefunden haben, darunter "externe" Templates, auto_ptr und Ausnahme-Spezifikationen (void foo() throw()), scheint es rückblickend eine weise Entscheidung gewesen zu sein, auf Konzepte vorerst zu verzichten. Hier hat man aus den Fehlern der Vergangenheit gelernt.

In einem weiteren Artikel [5] spricht Stroustrup davon, Konzepte in C++1y, den nächsten C++-Standard, aufzunehmen. Interessant (und nach Meinung des Autors notwendig angesichts der Schnelllebigkeit der IT-Industrie), ist der Zeitrahmen: Nach nur fünf statt wie zuletzt zwölf Jahren soll C++1y seinen Vorgänger beerben.

In der Zwischenzeit wird sich das Komittee voraussichtlich dem seit 2005 liegen gebliebenen Technical Report 2 (TR2) widmen, der C++11 mit neuen Standardbibliotheken bereichern soll, so wie es TR1 für C++98 erfolgreich vorgeführt hat. Bisher hat es die Boost.FileSystem-Bibliothek in den TR2 geschafft. Auch Boost.Asio (async I/O), eine Netzwerk-Bibliothek, steht bereits in den Startlöchern. Der Autor erwartet außerdem eine Fülle von Bibliotheken die sich dem Thema Multithreading widmen, so zum Beispiel eine Threadpool- oder generische Task-Bibliothek. Damit schlösse sich der Kreis wieder, denn eine der ersten Bibliotheken, die Stroustrup in seiner Sprache entwickelt hat, war eine ebensolche Task-Bibliothek.

Alle neuen C++11-Funktionen auch nur kurz skizzieren zu wollen, würde über das hinausgehen, was dieser Artikel zu leisten vermag. Deshalb seien nur die elf – nach Meinung des Autors – wichtigsten Neuerungen beschrieben:

Ohne Zweifel ist die in der Praxis wichtigste Neuerung (wenn auch schon vor Jahren mit Boost und TR1 eingeführt) std::shared_ptr, das Schweizer Taschenmesser unter den intelligenten Zeigerklassen. Das mag angesichts von Funktionen mit deutlich mehr "Sexappeal" verwundern, jedoch hat sich shared_ptr als unverzichtbares Ausdrucksmittel in der C++-Entwicklung erwiesen und ist wahrscheinlich für eine ganze Generation von C++-Entwicklern die "Einstiegsdroge" für Boost gewesen.

Es erhält seine Universalität durch den sogenannten "Custom Deleter", der, als zweites Konstruktorargument angegeben, nicht Teil des shared_ptr-Typs ist. Mit seiner Hilfe kann man den standardmäßigen delete-Aufruf zum Zerstören des Objekts durch selbst definierte Funktionen ersetzen. So legt der Entwickler zum Beispiel durch Angabe von fclose als Custom Deleter einen shared_ptr<FILE> an. Übergibt er eine leere Funktion, kann er das Zerstören sogar ganz verhindern, um beispielsweise static-Objekte wie Heap-Objekte zu behandeln.

Aber auch ohne explizite Angabe des Deleter-Arguments zeigt sich ein positiver Effekt: Es lässt sich eine Klasse ohne virtuellen Destruktor problemlos durch einen shared_ptr der Basisklasse zerstören (etwas, das bei nackten Zeigern sofort "undefiniertes Verhalten" auslöst), oder unter Windows eine Klasse in einer Debug-DLL erzeugen und in einer Release-DLL zerstören (ohne shared_ptr erhält man eine Heap-Korrumpierung), denn der Zerstörungscode läuft dank des Custom Deleter in der Debug-DLL.

shared_ptr<FILE> file( fopen( "file.txt", "rw" ), &fclose );
fwrite( "hi", 1, 2, file.get() ); // conversion to FILE* requires explicit get()
//..
// calls fclose( file.get() ) when last shared_ptr goes out of scope

Aus der Riege der wirklichen Neuerungen sticht auto hervor (Boost.Typeof, der Versuch einer reinen Bibliotheksumsetzung ohne explizite Sprachunterstützung, war nicht besonders anwenderfreundlich). Denn die Compiler enthalten den Code hierfür bereits (für den sizeof-Operator), es fehlte nur an einer einheitlichen Schnittstelle für den Programmierer. Mit auto und decltype steht diese nun bereit. Während decltype (typeof war durch zu viele inkompatible Erweiterungen der C++-Compiler-Bauer belegt, daher der etwas kryptische Name) wie sizeof ein Operator ist, der sich überall dort einsetzen lässt, wo ein Typ erwartet wird, verwendet man auto in Variablendefinitionen mit direkter Zuweisung, um sich die Angabe des genauen Typs der Variablen zu sparen:

auto i = 0; // i is an 'int'
const auto d = 0.0; // d is a 'const double'
auto b = bind( &func, _1, _2, 13 ); // 'b' cannot
// (portably) be declared!
for ( auto it = vec.begin() ; it != vec.end() ; ++it )

Daneben leitet auto auch die neue alternative Syntax für Funktionsdeklarationen ein, die sich im Gegensatz zur klassischen Variante dadurch auszeichnet, dass Zugriff auf die formalen Parameter besteht. Besonders interessant ist das ist das bei Funktions-Templates, denn dann ist die Angabe des Typs des Return-Werts meist nicht mehr so einfach möglich, vor allem, wenn die komplizierten C-Ganzzahl-Konvertierungsregeln abzubilden sind:

auto add( int a, int b ) -> int { return a + b; }
template <typename T, typename S>
auto add( T a, S b ) -> decltype(a+b) { return a + b; }

Bei Lambda-Funktionen (siehe unten) ist die Angabe des Rückgabetyps sogar optional, wenn die Funktion lediglich aus einer Rückgabeanweisung besteht. Das ist leider bei normalen Funktionen nicht erlaubt.

Neu ist die Möglichkeit, Datenfelder von Klassen direkt bei der Deklaration initialisieren zu können. Das ist besonders nützlich bei Zeigervariablen, denen man nun an Ort und Stelle Null zuweisen kann:

class Class {
// ...
private:
MyWidget * m_myWidget = 0;
double d = 1.0;
};

Die so angegebenen direkten Initialisierungswerte verwenden alle Konstruktoren, sie lassen sich in den Initialisierungslisten einzelner Konstruktoren aber dennoch individuell ersetzen.

Auch wenn die beiden Funktionen nicht deckungsgleich sind, sollen sie zusammengefasst werden, denn sie verfolgen das gleiche Ziel, die Anpassung von Standardalgorithmen an benutzerspezifische Situationen zu vereinfachen. Gemeint sind die zahlreichen Funktions- oder Prädikatparameter der "Standard Template Library"-Algorithmen (STL), zu deren effektiver Nutzung man bisher händisch Funktionsobjekte schreiben musste (bind1st und bind2nd sollen nur der Vollständigkeit halber erwähnt werden; ihre Verwendung stößt in der Praxis selbst bei sparsamer Anwendung schnell an Lesbarkeitsgrenzen).

Während bind() die im Standard enthaltenen bind1st und bind2nd durch eine einheitliche und deutlich nutzerfreundlichere Syntax ersetzt, sind Lambda-Funktionen eine neue Sprachfunktion, mit deren Hilfe sich kleine Funktionen an Ort und Stelle definieren lassen. Leider ist die Syntax etwas gewöhnungsbedürftig geraten, und wirklich kleine Funktionen werden durch die Argumentdeklaration sehr länglich (hier glänzt Boost.Lambda, eine Implementierung auf Bibliotheksebene, durch willkommenen Minimalismus). Nichtsdestoweniger sind Lambda-Funktionen ein wichtiger Baustein und eine willkommene Erleichterung für STL-affine Entwickler.

Hier nun ein Beispiel (vorher/nachher):

int f( int );
int g( int );
// int x-> f( g( x ) ) and int x -> ( x + 1 ) * 2 as functors:
// C++98:
compose1( ptr_fun( f ), ptr_fun( g ) )
bind2nd( multiplies<int>, bind2nd( plus<int>(), 1 ), 2 )
// C++0x bind()
bind( f, bind( g, _1 ) )
bind( multiplies<int>(), bind( plus<int>(), _1, 1 ), 2 )
// C++-0x lambdas
[] ( int x ) -> int { return f( g( x ) ); } // -> int is optional
[] ( int x ) { return ( x + 1 ) * 2; }
// Boost.Lambda
bind( f, bind( g, _1 ) )
( _1 + 1 ) * 2

Nicht nur für die HPC-Fraktion (High Performance Computing), sondern auch Embedded-Entwicklern und Programmierern, die gern statische, konstante Tabellen verwenden, um Daten und Code zu trennen, kommt constexpr wie gerufen. D-Programmierer werden dahinter ihr lieb gewonnenes pure-Schlüsselwort wiedererkennen, auch wenn die C++-Variante einen etwas anderen Ansatz verfolgt.

Eine Funktion ist „pur“, wenn sie allein von tatsächlichen Parametern abhängt, also nicht nur seiteneffektfrei ist, sondern auch keinerlei nichtkonstanten globalen Zustand verwendet. Eine C++-Funktion lässt sich als constexpr deklarieren, wenn sie „pur“ ist und nur aus einer Rückgabeanweisung (return statement) besteht, die selbst eine constexpr ist. Besonders interessant wird die Funktion dadurch, dass auch Konstruktoren constexpr sein können, wenn sie nur aus der Konstanten-Initialisierungsliste bestehen (also einen leeren Rumpf haben) und die Ausdrücke in der Initialisierungsliste constexpr sind. Ein Beispiel zeigt das folgende Listing.

class Point {
int px, py;
public:
constexpr Point() : px(0), py(0) {}
constexpr Point( int x, int y ) : px(x), py(y) {}
constexpr int x() const { return px; }
constexpr int y() const { return py; }
};
class Rect {
int x1, y1, x2, y2;
public:
constexpr Rect() : x1(0), y1(0), x2(0), y2(0) {}
constexpr Rect( int x, int y, int w, int h )
: x1(x), y1(y), x2(x+w-1), y2(y+h-1) {}
constexpt Rect( const Point & p1, const Point & p2 )
// ...provided std::{min,max} are constexpr...
: x1( std::min( p1.x(), p2.x() ) ),
y1( std::min( p1.y(), p2.y() ) ),
x2( std::max( p1.x(), p2.x() ) ),
y2( std::max( p1.y(), p2.y() ) ) {}
// ...
};
constexpr Rect rects[] = {
Rect( 0, 0, 100, 100 ),
Rect( Point( -10, -10 ), Point( 10, 10 ) ),
};

Die durchgängige constexpr-Kette erlaubt dem Compiler, das rects-Feld zur Übersetzungszeit zu berechnen, und dem Linker, es im Nur-Lese-Speicher abzulegen. Der Autor erwartet, constexpr in seinen Projekten ähnlich oft zu verwenden wie das klassische const: nämlich überall dort, wo es zulässig ist.

Zum Schluss sei noch auf den fundamentalen Unterschied zu Ds pure hingewiesen: Während pure dem D-Compiler Informationen aus anderen Übersetzungseinheiten zur Verfügung stellt, um CSE (Common Subexpression Elimination) zu ermöglichen, ist constexpr genau genommen redundant. Ein guter Optimierer würde den oben beschriebenen Effekt auch ohne constexpr bewerkstelligen, zumal der Standard verlangt, dass Rümpfe von constexpr-Funktionen in jeder Übersetzungseinheit sichtbar sind. Der Vorteil des Schlüsselworts liegt jedoch darin, dass der Programmierer dem Compiler damit seine Intention vorgeben kann und eine Rückmeldung (Fehler) erhält, wenn der Compiler herausfindet, dass er der Vorgabe nicht folgen kann.

Range-based for-loops, so die englische Bezeichnung, vereinfachen das vollständige Iterieren über Container, ähnlich wie foreach in vielen anderen Sprachen:

const std::vector<int> vi = ...;
for ( int i : vi ) // read: for each i in vi
...;

Auch hier darf der Entwickler faul sein und auto schreiben:

const auto vi = ...;
for ( auto i : vi )
...;

Die Syntax funktioniert mit allen Containern, die begin()- und end()-Methoden enthalten.

Eine statische Zusicherung (static_assert) funktioniert wie das klassische assert()-Makro, jedoch zur Übersetzungszeit und nicht zur Laufzeit. Damit wäre eigentlich alles gesagt, die Nützlichkeit stellt schließlich BOOST_STATIC_ASSERT seit Jahren in der Praxis unter Beweis.

Provokant weit unten in den Top 11 steht die Multithreading-Unterstützung in C++11. Das liegt darin begründet, dass Entwickler mit C++ seit Dekaden ohne Unterstützung des Standards Programme schreiben, die Multithreading verwenden. Damit seien das moderne Speichermodell und die neuen Thread-Klassen nicht abgewertet. Wer jedoch Multithreading in C++ betreibt, dem helfen Pthreads, Boost. Thread, oder OpenMP schon seit Jahren bei der Arbeit, und mit Ausnahme von Boost.Thread unterscheiden sich die Ansätze derart stark von std::thread & Co.,dass nur wenige Projekte es sich leisten können, in absehbarer Zeit auf die Standard-Werkzeuge zu portieren.

Copy-Konstruktoren sind integraler C++-Bestandteil. Es ist aber interessant zu beobachten, dass der Entwickler in vielen Fällen, in denen die Sprache den Aufruf des Copy-Konstruktors vorschreibt, tatsächlich keine Kopie herstellen, sondern lediglich den Wert verschieben wollte. Sei es nun das Zurückliefern einer lokalen Variablen als Teil eines return-Ausdrucks, das Einfügen eines Werts in einen std::vector, die merkwürdige Eigentümerwechsel-Semantik des std::auto_ptr oder die Implementierung des primären std::swap()-Funktions-Templates. In diesen Situationen ist es unnötig, die Quellvariable intakt zu halten. Für einige der Fälle hat der C++-Standard Spezialfälle enthalten. So darf der Compiler den Copy-Konstruktor in einem return-Ausdruck wegoptimieren und das Objekt direkt in der Zielspeicherstelle anlegen, und bei std::auto_ptr sorgt eine komplizierte Implementierung auf Bibliotheksebene für den gewünschten Effekt.

C++11 stellt nun Sprachmittel bereit, die das Verschieben zu einer Erste-Klasse-Operation werden lässt. Neben Copy-Konstruktor und -Zuweisungsoperator kann der Klassenautor nun Move-Konstruktoren und -Zuweisungsoperatoren implementieren.

class Movable {
MovablePrivate * d;
public:
// ...
// copy semantics:
Movable( const Movable & other )
: d( other.d )
{
if ( d ) d->ref();
}
Movable & operator=( const Movable & other ) {
if ( other.d ) other.d->ref();
if ( d ) d->unref();
d = other.d;
return *this;
}
// move semantics:
Movable( Movable && other )
: d( other.d )
{
other.d = 0;
}
Movable & operator=( Movable && other ) {
std::swap( d, other.d );
return *this;
}
// ...
};

Möglich machen das sogenannte Rvalue-Referenzen (&&). Sie binden im Gegensatz zu klassischen Referenzen (&) nicht nur an Lvalues, sondern eben auch an Rvalues. Lvalues sind Ausdrücke, denen man etwas zuweisen kann (sie können links vom Zuweisungsoperator stehen), Rvalues alle anderen (sie können nur rechts vom Zuweisungsoperator stehen).

Im Beispiel vereinfacht sich der Move-Zuweisungsoperator dadurch, dass der Entwickler nur die d-Zeiger tauscht. Das ist deshalb legitim, weil er eine Verschiebung implementiert, die other nicht in einem sinnvollen Zustand belassen muss (nur in einem konsistenten). Das ist effizienter als der Copy-Zuweisungsoperator, der beide d-Zeiger referenzieren muss und mehrere Sprünge enthält.

Zur Umwandlung von Lvalues in Rvalue-Referenzen stellt der Standard die std::move()-Funktion zur Verfügung.

Variadische Funktions- und Klassen-Templates vereinfachen den Entwurf von Klassen
wie std::tuple oder von Funktionen wie einem C++-printf. Sie sind daher in erster Linie für Bibliotheksentwickler und weniger für Anwendungsentwickler interessant.

Als Abschluss der Top 11 seien noch Template-Typedefs genannt. Anders als die vielleicht erwartete Syntax

template <typename T>
typedef std::vector<T,MyAlloc<T>> MyAllocVector;

einzuführen, hat sich das C++-Komitee entschieden, typedef gleich durch eine einfachere Syntax zu ersetzen. Jeder, der schon mal einen Funktionszeiger in C/C++ deklarieren musste (und insbesondere alle, die das bisher noch nicht mussten), werden die neue Syntax zu schätzen wissen:

template <typename T>
using MyAllocVector<T> = std::vector<T,MyAlloc<T>>;

Damit kann man nun auch schreiben:

using myint32 = int;
using myfunptr = int(*)( double );
using mymemfunptr = int(Class::*)( double );

Die äquivalenten Deklarationen mit typedef seien dem geneigten Leser hiermit zur Abschreckung als Übung gestellt.

Auch wenn C++ keine beliebte Sprache ist, wird sie Entwickler noch eine ganze Weile begleiten. Tatsächlich könnte sich generell der Bedarf an C++ beziehungsweise an hardwarenahen Sprachen in nächster Zeit erhöhen; vor allem wenn die Hardware in Sachen Parallelisierung den zu ihrer Programmierung notwendigen Softwarekonzepten weiterhin davonläuft.

Die spannendste Frage wird sein, wie schnell sich C++11 gegen seinen Vorgänger C++98 wird durchsetzen können. Es war in den vergangenen zwei Dekaden regelmäßig notwendig, C++-Neuerungen konservativ einzusetzen, da Compiler den Standard nur zögerlich umgesetzt haben. So wird erst GCC 4.7 den zweistufigen Instantiierungsprozess von Templates korrekt implementieren, vermutlich auf Druck von Clang, dem Compiler des LLVM-Projekts (Low-Level Virtual Machine), der seit Monaten vormacht, wie man's richtig macht. Der Autor hat leidvolle Erfahrungen mit Firmen hinter sich, in denen der Einsatz der STL oder Boosts eine endlose Diskussion mit sich geführt hat. Hier muss sich die C++-Gemeinde umgewöhnen. Neuerungen wie Lambdas und auto machen C++11 zu einer deutlich produktiveren Sprache, als C++98 es war. Es gilt nun, diese Produktivitätssteigerung im Alltag auch zu nutzen, um gegen Java und .NET zu bestehen. Ein weiteres Jahrzehnt der Zersplitterung und Inkompatibilität könnte die momentan exzellente Ausgangsposition von C++ schnell wieder zunichte machen.

Glücklicherweise steht es mit der C++11-Unterstützung in aktuellen Compilern gut (GCC [6], VC++ [7], Clang [8], Edison Design Group [9] (Frontend für die meisten anderen C++-Compiler)). Mit GCC und Visual C++ kann der Entwickler die wichtigsten Neuerungen heute produktiv einsetzen, auch wenn GCC noch zusätzliche Kommandozeilenargumente (-std=c++0x) erzwingt, um die Neuerungen freizuschalten (Microsoft schaltet sie bei VS2010 standardmäßig ein). Sorge bereitet einzig Apple, die sich durch den Umstieg von GCC auf Clang von der Entwicklung ein wenig abgekoppelt haben. Es bleibt zu hoffen, dass die Clang-Entwickler im Laufe der nächsten Monate zu Microsoft, EDG und GCC aufschließen werden. Der Autor ist hier optimistisch, dass die Firmen schnell feststellen werden, dass "C++98-Unterstützung jetzt extra kostet [10]".

Marc Mutz
arbeitet als Senior Software Engineer, Trainer und Consultant bei KDAB (Deutschland) GmbH & Co. KG.
(ane [11])


URL dieses Artikels:
https://www.heise.de/-1345406

Links in diesem Artikel:
[1] http://channel9.msdn.com/posts/C-and-Beyond-2011-Herb-Sutter-Why-C
[2] http://drdobbs.com/cpp/217500206
[3] http://www10.informatik.uni-erlangen.de/~pflaum/pflaum/ProSeminar/exprtmpl.html
[4] http://www.open-std.org/Jtc1/sc22/wg21/docs/papers/2009/n2906.pdf
[5] http://www.ddj.com/cpp/218600111
[6] http://gcc.gnu.org/projects/cxx0x.html
[7] http://blogs.msdn.com/b/vcblog/archive/2010/04/06/c-0x-core-language-features-in-vc10-the-table.aspx
[8] http://clang.llvm.org/cxx_status.html
[9] http://www.edg.com/docs/edg_cpp.pdf
[10] http://marcmutz.wordpress.com/2011/09/20/c98-support-costs-extra/
[11] mailto:ane@heise.de