Gegenwärtig entsteht der neue C++-Standard. Die GCC-Versionen 4.3 und 4.4 implementieren bereits einige Features davon. Sie lassen sich über die Option »-std=c++0x« aktivieren und sind vielfach schon so ausgereift, dass jedermann sie ohne Bedenken einsetzen kann .
Benutzer der Boost-Bibliotheken [1] kennen die meisten der neuen Features schon, denn ein guter Teil des neuen Standards gründet sich auf Boost. Die Einzelheiten finden sich im Draft des C++-Standards [2] und im Technischen Report zur Standardlibrary TR1 [3].
Neue Funktionen
Schon der C99-Standard vergrößerte die Anzahl der C++-Funktionen in »math.h« stark. Der neue Standard enthält in der Bibliothek »cmath« alle Funktionen aus C99 (Tabelle 1) und ergänzt noch viele weitere praktische Funktionen (Tabelle 2), weitere steuert die neue Standardbibliothek bei (Tabelle 3).
Außerdem schreibt er vor, dass die neuen C-Bibliotheken wie beispielsweise »stdint.h« oder »fenv.h« auch in C++ verfügbar sein müssen. Die C++-Namen lauten »cstdint«, »cfenv« und so weiter. Alle Definitionen sind im Namensbereich »std« verpackt.
|
Tabelle 1: |
|---|
Hinzugenommen sind auch mehrere neue Container. Zunächst wären da die Hash-basierten Varianten der assoziativen Container »set«, »multiset« und »map« zu nennen. Eigentlich waren diese Container schon für den alten Standard geplant, die Entwickler ließen sie damals aber aus Zeitmangel aus.
Im neuen Standard gibt es die Bibliotheken »unordered_set« und »unordered_map«, die die Hash-basierten Container »unordered_set«, »unordered_multiset«, »unordered_map« und »unordered_multiset« definieren. Der Namensteil “unordered” deutet darauf hin, dass für die Benutzung dieser Container kein Vergleichsoperator nötig ist. Die Definition der Klasse »unordered_set« lautet:
template<class Value,
class Hash=hash<Value>,
class Pred=std::equal_to<Value>,
class Alloc=std::allocator<Value> >
class unorderd_set {
...
}
|
Tabelle 2: Neue |
|---|
|
Tabelle 3: |
|---|
Man kann also über die Template-Parameter den Allokator auswählen (wie bei allen Standardcontainern), das Vergleichsprädikat festlegen (standardmäßig kommt »==« zum Zug) und die Hashfunktion bestimmen. Wenn keine besonderen Informationen über die Daten vorliegen, fährt man mit den Standardeinstellungen sehr gut.
Der Vorteil der Hash-basierten Container gegenüber den Baum-basierten ist ihre größere Geschwindigkeit (siehe Tabelle 4). Ebenfalls neu sind die Klassen »array« (eine leichtgewichtige Klasse um ein C-Array) und die Klasse »forward_list« (eine einfach verkette Liste).
|
Tabelle 4: |
|---|
Intelligente Zeiger
Der C++-Standard kennt mit »auto_ptr« einen Pointer, der sein Objekt am Ende seiner Gültigkeit automatisch löscht. Die Klasse »auto_ptr« hat jedoch viele Einschränkungen. Jedes Objekt kann nur einen Besitzer haben und Kopier-Operationen führen zu sehr merkwürdigem Verhalten. Insbesondere darf der Programmierer »auto_ptr« nicht in Standardcontainern aufbewahren. Effektiv macht »auto_ptr« also mehr Probleme, als es löst. Der neue C++-Standard schafft »auto_ptr« zugunsten verbesserter intelligenter Zeiger ab. Die neuen sind:
- »unique_ptr« ähnelt am meisten dem alten
»auto_ptr«. Das Objekt gehört ausschließlich
zu dem Zeiger. Mit »unique_ptr« lässt sich
ausnahmesicher ein Zeiger innerhalb einer Funktion
aufbewahren. - »shared_ptr« ist ein universell einsetzbarer
intelligenter Zeiger. Mehrere »shared_ptr« können
sich ein Objekt teilen. Löscht der Programmierer den letzten
»shared_ptr«, der auf ein Objekt zeigt, ruft sein
Dekonstruktor den Dekonstruktor des Objekts auf. - »weak_ptr« beobachtet Objekte, die
»shared_ptr« verwaltet. Der »weak_ptr« hat
keine Rechte an dem Objekt. Es ist also die Aufgabe des Anwenders
sicherzustellen, dass das Objekt mindestens so lange existiert wie
der Zeiger. Die beiden zuletzt genannten Zeiger stammen aus dem
Boost-Projekt.
Variable Anzahl Argumente
Viele Erweiterungen des neuen C++-Standards betreffen nur die Bibliotheken. Es gibt jedoch auch mehrere Änderungen am Sprachkern. Die meisten sind eher kosmetischer Natur. Zum Beispiel erkennt der Parser jetzt spitze Klammern in geschachtelten Templates wie »vector<vector<int>>«. Früher musste man »vector<vector<int> >« schreiben, um eine Verwechslung mit dem Links-Shift-Operator »>>« auszuschließen.
Die vielleicht gravierendste Änderung am Sprachkern bringen die Templates mit variabler Anzahl von Parametern. Eine mögliche Anwendung ist die Implementierung einer typsicheren Tuple-Klasse wie im folgenden Beispiel:
template<typename... REST>
struct tuple;
template<>
struct tuple<>{};
template<typename T>
struct tuple<T>{ T car;};
template<typename T, typename... REST>
struct tuple<T,REST...> { T car; U
tuple<REST...> cdr;};
int main() {
tuple<int, char> t;
t.car = 1;
t.cdr.car = 'a';
}
Eine im Vergleich wesentlich ausgefeiltere Tupel-Klasse stellt übrigens die neue Standardbibliothek »tuple« bereit.
Der schon von C übernommene Zufallsgenerator war nur für einfachste Simulationen gedacht. Die neue Bibliothek »rand« geht jetzt weit darüber hinaus. Der Entwickler kann unter verschieden Methoden zur Generierung von Pseudozufallszahlen (lineare Kongruenzen, Mersenne-Twister und andere) auswählen. Dabei lassen sich alle Parameter frei wählen. Wer sich jedoch nicht sehr gut mit Zufallsgeneratoren auskennt, fährt besser mit einer der vielen voreingestellten Methoden.
Zufallszahlen
Außer der Methode für die Erzeugung der Pseudozufallszahlen darf der Entwickler auch die gewünschte Verteilung aus einem großen Angebot frei wählen (gleichförmige Verteilung, Normalverteilung und so weiter).
Die GCC-Implementierung der Bibliothek »rand« ist noch nicht vollkommen. Die Abweichungen vom künftigen Standard sind allerdings eher kosmetischer Natur und verschwinden sicherlich in den kommenden Versionen.
Typetraits
Wer viel mit Templates programmiert, stolpert früher oder später über folgendes Problem: Er möchte eine Template-Klasse oder -Funktion schreiben, aber der Code funktioniert nur, wenn der Typ zusätzliche Eigenschaften hat. Die Bibliothek »type_traits« bietet jetzt eine elegante Möglichkeit, so etwas zu testen, zum Beispiel:
template<typename T> T f(T a) {
static_assert(std::is_floating_point<T>U
::value == true, "f needs a floating U
point argument!");
...
}
Der Befehl »static_assert« ist ebenfalls neu und definiert eine Zusicherung zur Compilezeit. Eine komplexe Anwendung von Typetraits bietet der Standardbefehl »copy()«. Eine naive Implementierung ruft stets den Copy-Konstruktor der beteiligten Objekte auf. Eine clevere dagegen benutzt bei einfachen Typen das schnellere »memcpy()«. Eine Implementierung von »copy()« könnte so aussehen:
template<typename I1, typename I2, bool b>
I2 copy_imp(I1 first, I1 last, I2 out, U
const integral_constant<bool, b>&)
{
while(first != last)
*(out++) = *(first++);
return out;
}
template<typename T>
T* copy_imp(const T* first, const T* $$
last, T* out, const true_type&)
{
memcpy(out, first, (last-first)*sizeof(T));
return out+(last-first);
}
template<typename I1, typename I2>
inline I2 copy(I1 first, I1 last, I2 out)
{
typedef typename iterator_traits<I1>::U
value_type value_type;
return copy_imp(first, last, out, U
has_trivial_assign<value_type>());
}
Die vorgestellte Implementierung ist noch immer etwas vereinfacht. Wenn der Programmierer Zugriff auf die interne Struktur der Standardcontainer hat, kann und sollte er auch Iteratoren von Vektoren gesondert behandeln.
Parallelisierung
Alle bisher genannten Features sind in der Version 4.3 des GCC nutzbar, die den meisten Distributionen beiliegt. Die nun folgenden Features sind in der neuesten Version 4.4 hinzugekommen. Die wichtigste Neuerung ist hier die verbesserte Unterstützung für Multithreading. Die Klasse »thread« aus der gleichnamigen Bibliothek stellt Methoden zur Thread-Verwaltung bereit. Das folgende Beispiel zeigt, wie neue Threads starten und der Vater auf die Kind-Threads wartet:
vector<thread *> active_threads(thread_num);
// Starte threads
for(unsigned int tn=0; tn<thread_num; tn++)
active_threads[tn] = new thread(worker,tn);
// Warte auf die Kindthreads
for(unsigned int tn=0; tn<thread_num; tn++) {
active_threads[tn]->join();
delete active_threads[tn];
}
Die Bibliothek »mutex« definiert Lockingmechanismen. Ein Beispiel ist:
mutex access_the_file; // Globale Variable
void a_function_that_runs_in_several_U
threads() {
...
access_the_file.lock();
// Diesen Bereich können mehrere Threads
// nicht gleichzeitig betreten
...
access_the_file.unlock();
...
}
Außerdem verbesserte sich die Unterstützung für OpenMP. Beispiel:
std::vector<int> vec(128);
#pragma omp parallel for default(none)U
shared(vec)
for (std::vector<int>::iterator it = U
vec.begin(); it < vec.end(); it++)
{
// arbeite mit dem Iterator it
}
Jetzt darf man außer Schleifen mit Schleifenvariablen vom Typ Int auch Schleifen mit STL-Iteratoren parallelisieren.
Ausblick
Die Entwicklung von C++ ist noch nicht abgeschlossen, besonders interessant für die meisten Anwender dürften zwei Punkte sein:
- Reguläre Ausdrücke: Diese Funktion ist in GCC 4.4.0
noch nicht implementiert. - Atomare Operationen: Dieser Teil des Standards scheint noch in
aktiver Entwicklung zu sein. (jcb)
|
Infos |
|---|
|
[1] Boost-C++-Libraries: [http://www.boost.org] [2] ISO/IEC 14882: [http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2798.pdf] [3] ISO/IEC TR 19768: [http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2005/n1836.pdf] |










