C++09 (Teil 2) - Ein Überblick: Die Standardbibliothek
-
C++09 (Teil 2) - Ein Überblick: Die Standardbibliothek
Dies ist der zweite Teil der Serie über den wahrscheinlich 2009 erscheinenden neuen Standard für C++.
Mit dem Technical Report (TR1) hat schon 2005 zuallererst die Standardbibliothek ein neues Gesicht bekommen, vor allem Boost diente dabei als hervorstechende Quelle der Inspiration. Der Technical Report 1 wird in C++09 vollständig enthalten sein, auch wenn teilweise Namen verändert und neue Funktionalitäten hinzugefügt werden.
Damit wird die Standardbibliothek von C++09 jener Bereich sein, in dem die meisten Neuerungen stattfinden werden. Der Bereich im C++-Standard, der ihr gewidmet ist hat sich allein von der Seitenanzahl her jetzt schon seit C++03 beinahe verdoppelt (von knapp unter 400 Seiten auf über 700 Seiten im aktuellen Working-Draft).
Der Hauptteil besteht dabei aus Erweiterungen aus dem TR1, ein weiterer großer Teil beschreibt die Unterstützung von paralleler Verarbeitung (Multithreading-Bibliothek und atomare Operationen). Ich möchte hier nicht allzu sehr ins Detail gehen, vor allem nicht Details erklären oder Schnittstellenspezifikationen aufschreiben, dafür gibt es ja den Standard selbst. In erster Linie möchte ich Beispiele geben, die zukünftige Möglichkeiten der Standardbibliothek aufzeigen.
Hinweis: Ich verwende in den Code-Beispielen bewusst exemplarisch neue Sprachfeatures und damit die teilweise recht ungewohnte Syntax von C++09. Dazu gehört vor allem:
- Die einheitliche Form der Initialisierung mit geschwungenen Klammern
- Variadic Templates
- Rvalue-Referenzen
- Die neue Form der Funktionsdeklaration
Für ein besseres Verständnis des Codes ist daher die Lektüre der entsprechenden Kapitel aus dem ersten Teil der Reihe (C++09 - Ein Überblick: Sprachfeatures) empfehlenswert.
Inhalt
- 1 Erweiterungen aus dem TR1
- 1.1 Utilities
- 1.2 Funktionsobjekte
- 1.3 Neue Container
- 1.4 Type-Traits
- 1.5 Reguläre Ausdrücke
- 1.6 Zufallszahlen
- 2 Multithreading
- 2.1 Threads
- 2.2 Mutexe und Monitore
- 3. Datum und Zeit
- 4 System-Fehler
- 5 Integration neuer Sprachfeatures
- 5.1 Standard-Concepts
- 5.2 Noch mehr:
std::string
- 6 Erweiterungen für den TR2 und danach
- 6.1 Filesystem
- 6.2 Weitere mögliche Neuerungen
- 7 Ausblick
- 8 Verweise
1 Erweiterungen aus dem TR1
Der Technical Report 1 ist ein Dokument, in dem verschiedene Erweiterungen der C++-Standardbibliothek vorgeschlagen werden. Der TR1 wurde 2005 veröffentlicht und viele der Funktionen werden von aktuellen Compilern (bzw. Standardbibliotheken) bereits unterstützt, teils auch bereits mit Hilfe der neuen Sprachfeatures.
1.1 Utilities
Der TR1 definiert einige nützliche Klassen, die das Leben in vielen Bereichen einfacher machen. Zu ihnen gehören
- Reference Wrapper. Diese sind nützlich, um Referenzen an Funktionstemplates zu übergeben, die eigentlich Kopien ihrer Argumente anfertigen würden.
- Smart Pointer. Zeiger, die sich selbst um die Freigabe des Speichers kümmern.
- Tuple. Ein Container fixer Länge, der Daten verschiedenen Typs aufnehmen kann.
Beispiel
#include <functional> //Reference-Wrapper #include <memory> //Smart-Pointer #include <tuple> //Tuple #include <vector> using namespace std; template <class T> void fun (T t) { t += 21; } int main () { int i = 21; fun (ref(i)); //Einen Reference-Wrapper erstellen //i == 42 auto p = make_shared<int>(42); //new int(42) für Smart-Pointer *p = 23; //p verhält sich wie ein Zeiger. //Nur: das delete geschieht automatisch. //Dynamischen std::vector mit Inhalt 1, 2, 3, 4 erzeugen: auto vi = make_shared<vector<int>>({1,2,3,4}); //Beliebig viele verschiedene Typen in einem Tupel speichern: auto foo = make_tuple(5, 4.2, 2.3); auto bar = make_tuple("std::string"s, 'c'); //Zwei tuple-Instanzen vereinigen auto fubar = concatenate(foo, bar); //Werte auslesen auto first = get<1>(foo); //decltype(first) -> int auto second = get<2>(foo); //decltype(second) -> double } //Funktion, die mehrere Rückgabewerte hat: auto fun () -> tuple<int, vector<int>, string> { return make_tuple(5, { 5,4,3,2,1 }, "foobar"s); }
Erläuterung
Wie der Header<functional>
schon vermuten lässt, werden Reference-Wrapper hauptsächlich in Verbindung mit Funktionen höherer Ordnung verwendet. Ein weiteres Beispiel hierzu findet sich in 1.2.Smart-Pointer sind schon eine Weile im Umlauf. Der Standard von 2003 bot bereits einen:
std::auto_ptr
. Dieser hatte jedoch nicht immer die Semantik, die man erwartet hätte (Move-Semantik statt Kopiersemantik). Sein größter Nachteil war, dass er nicht in Standard-Containern verwendet werden konnte.std::auto_ptr
ist mit C++09 deprecated und kann durchstd::shared_ptr
bzw.std::unique_ptr
ersetzt werden, je nach Aufgabe.std::tuple
ist dazu gedacht,std::pair
zu ersetzen, denn es kann nicht nur zwei sondern beliebig viele Werte unterschiedlichen Typs aufnehmen.std::pair
wird mit C++09 ebenfalls deprecated sein.Status
Der TR1 ist Teil des aktuellen Working Drafts des kommenden C++-Standards.Unterstützung
Der TR1 wird von allen größeren Standardbibliothek-Implementierungen unterstützt. Da der TR1 verbietet, dass seine Klassen und Funktionen direkt in den Standardheadern eingebaut werden, muss man diese zumeist explizit aktivieren (entweder über ein Makro oder durch einen anderen Include-Pfad, bspw.<tr1/functional>
. Zu beachten ist auch, dass bestimmte Funktionen wie etwamake_shared
nicht im TR1 enthalten ist, da es auf Rvalue-Referenzen basiert (siehe Teil 1 der Reihe). Zumindest die libstdc++ implementiert in Version 4.3 einige Funktionalität mithilfe der neuen Sprachfeatures (Dazu muss man beim Kompilieren-std=c++0x
aktivieren). Allerdings gibt es keine vollständige Implementierung des TR1.Proposals
N1403 - Tuple
N1450 - Smart-Pointer
N1453 - Reference-Wrapper
N1836 - Der Draft des TR11.2 Funktionsobjekte
Bereits C++03 bot einige Funktionen höherer Ordnung an, das sind Funktionen, die andere Funktionen als Argument erwarten und mit ihnen umgehen. Die Verwendung von ihnen war jedoch oft nur eingeschränkt möglich. Mit
std::function
gibt es nun einen polymorphen Funktionswrapper, der auch Zeiger auf Memberfunktionen von Klassen beinhalten kann. Außerdem wurde die Beschränkung vonstd::bind1st
undstd::bind2nd
, Argumente jeweils nur an den ersten oder zweiten Parameter binden zu können zu Gunsten vonstd::bind
, das mit Funktionen mit unbestimmter Anzahl an Parametern arbeitet, aufgegeben.Beispiel
#include <functional> //Binder, Funktionsobjekte #include <algorithm> using namespace std; //Addiert eine Zahl zu einer anderen: template <typename T> int accumulate (T value, T& acc) { return acc += value; } //Addiert zwei Zahlen und zählt mit, wie oft es aufgerufen wurde template <class T> struct Adder { int count; Adder () : count(0) {} T add (T a, T b) { ++count; return a + b; } }; int main () { vector<int> v1 { 1,2,3 }; int value = 0; for_each (v1.begin(), v1.end(), bind(accumulate<int>, placeholders::_1, ref(value))); //value == 1 + 2 + 3 //Mache aus der Function "accumulate" eine neue Funktion, die eine Zahl inkrementiert: function<int(int&)> incrementor = bind(accumulate<int>, 1, placeholders::_1); incrementor(value); //value == 7 //Auch Elementfunktion können verwendet werden: Adder<int> x; //Addiert 5 zu jedem Element transform (v1.begin(), v1.end(), v1.begin(), bind(&Adder<int>::add, &x, placeholders::_1, 5)); //x.count == 3 //v = { 6, 7, 8 } }
Erläuterung
std::bind
ist dazu gedacht, die Standardfunktionenstd::bind1st
undstd::bind2nd
zu ersetzen. Um dies zu erreichen, werden "Platzhalter" eingeführt. Aus einer Funktion, die also eigentlich zwei Argumente benötigt, kann auf diese Art und Weise eine unäre Funktion gemacht werden (s.u.) - genauso können die Parameter einer Funktion mithilfe dieser Platzhalter quasi vertauscht werden. Die Platzhalter sind dabei durchnummeriert (std::placeholders::_1
,std::placeholders::_2
, ...), wobei die Zahl den jeweiligen Parameter bezeichnet, den das so entstehende Funktionsobjekt übernimmt.void foo_binary (int, int); function<void (int)> foo_unary = bind (foo_binary, 42, placeholders::_1); //Platzhalter placeholders::_1 bedeutet also, dass das Funktionsobjekt //foo_unary genau ein Argument übernehmen kann. Dieses wurde mittels bind an //den zweiten Parameter der ursprünglichen Funktion, foo_binary, gebunden. //Das erste Argument, mit dem foo_binary aufgerufen wird, ist im Gegensatz //dazu kein Platzhalter, sondern schlicht und einfach "42". foo_unary (23); //gleichbedeutend mit: foo_binary (42, 23); function<void (int, int)> foo_reversed = bind (foo_binary, placeholders::_2, placeholders::_1); //Hier wird das erste Argument, mit dem das Funktionsobjekt aufgerufen wird, //(placeholders::_1) an den zweiten Parameter der ursprünglichen Funktion //gebunden; analog dazu wird das zweite Argument, mit dem das Funktionsobjekt //aufgerufen wird (placeholders::_1), an den ersten Parameter von foo_binary //gebunden. foo_reversed (1, 2); //gleichbedeutend mit: foo_binary (2, 1);
Erläuterung
Da C++09 jedoch direkt Closures (Lambda-Funktionen) unterstützt, werden viele Bereiche, in denen momentan boost.bind eingesetzt wird, durch diese übernommen. So lässt sich obiges Beispiel mit Closures auf folgende Art und Weise implementieren:#include <functional> //Binder, Funktionsobjekte, std::reference_closure #include <algorithm> using namespace std; int main () { vector<int> v1 { 1,2,3 }; int value = 0; for_each (v1.begin(), v1.end(), [&value](int element) (value += element)); //value == 1 + 2 + 3 //Speichere ein (Reference-)Closure reference_closure accumulate = [](int value, int& acc) -> int { return acc += next; }; //Mache aus dem Closure "accumulate" eine neue Funktion, die eine Zahl inkrementiert: function<int(int&)> incrementor = bind(accumulate, /* inkrementiere um */ 1, placeholders::_1); incrementor(value); //value == 7 }
Erläuterung
Dieses Beispiel zeigt, dass sich mit Closures viele Gebiete, die derzeit von umständlich zu erstellenden Funktionsobjekten abgedeckt werden, einfacher implementieren lassen.Status
siehe 1.1Unterstützung
siehe 1.1
Closures und damit auchstd::reference_closure
werden allerdings noch von keinem Compiler unterstützt.Proposals
N1402 - Polymorphe Funktionsobjekt-Wrapper
N1836 - Der Draft des TR11.3 Neue Container
TR1 definiert drei neue Container, die dieselbe Schnittstelle wie die anderen Standardcontainer anbieten. Es sind dies:
std::array
für nicht-dynamische Arrays fixer Größestd::unordered_set
, die gehashte Version vonstd::set
std::unordered_map
, die gehashte Version vonstd::map
- C++09 enthält außerdem noch einen zusätzlichen Container,
std::forward_list
Beispiel
#include <array> #include <unordered_map> #include <unordered_set> #include <forward_list> int main () { std::array<int, 20> array; std::unordered_map<string, int> unordered_map; std::unordered_set<double> unordered_set; std::forward_list<int> li = { 1, 2, 3, 4 }; }
Erläuterung
std::unordered_map
undstd::unordered_set
lassen sich – vom Interface her – ebenso verwenden wie die Standardcontainerstd::map
undstd::set
. Statt einen Vergleich mit dem Kleiner-als-Operator durchzuführen (standardmäßiges Verhalten beistd::map
undstd::set
), wird hier eine Hash-Funktion verwendet. Um mit benutzerdefinierten Typen arbeiten zu können, muss daher eine Spezialisierung vonstd::hash
(Header<hash>
) implementiert werden.
std::forward_list
ist eine einfach verkettete Liste, im Gegensatz zustd::list
, die doppelt verkettet ist.Status
siehe 1.1Unterstützung
siehe 1.1Proposals
N1456 - Hashtables
N1548 - Array
N1836 - Der Draft des TR1
N2545 - Singly-linked list1.4 Type-Traits
Type-Traits sind ein beliebtes Mittel der Template-Metaprogrammierung, das hilft, generischen Typen bestimmte Constraints zu setzen. Ob und wie weit sich dies durch den Einsatz von Konzepten (siehe hierzu Teil 1 der Reihe) ersetzen lässt, wird sich zeigen.
Beispiel
#include <type_traits> using namespace std; template <typename T, class Base, class Derived> struct Foo { static_assert (is_integral<T>::value, "T ist nicht integral!"); static_assert (is_base_of<Base, Derived>::value, "Erwarte voneinander abgeleitete Typen!"); }; template <typename T> void foo (T const& t, typename enable_if<is_pod<T>>::type * = 0) { } struct Virtual { virtual ~Virtual () = default; }; int main () { //OK, 5 ist plain-old-data foo(5); //Ups, polymorphe Klasse ist kein POD - Kompilierfehler foo(Virtual()); }
Erläuterung
Es stellt sich bei Type-Traits natürlich die Frage, inwieweit sie durch Konzepte (siehe Teil 1 der Reihe) abgelöst werden. Diese bieten ein eleganteres Interface, um Template-Parametern gewisse Constraints aufzuerlegen.Status
siehe 1.1
Im TR1 allerdings nicht enthalten sindstd::enable_if
,std::conditional
undstd::decay
. Diese sind allerdings im Working-Draft des kommenden C++-Standards beinhaltet.Unterstützung
Die im TR1 nicht enthaltenen Type-Traits befinden sich auch nicht im Namensbereichstd::tr1
. Als Alternative lassen sich bisweilen die entsprechenden Type-Traits aus der Boost-Bibliothek verwenden. Fürstd::decay
gibt es allerdings meines Wissens nach noch keine Implementierung.Proposals
N1424 - Type Traits
N1836 - Der Draft des TR1
N2240 -std::enable_if
undstd::conditional
N2244 -std::decay
1.5 Reguläre Ausdrücke
Reguläre Ausdrücke oder "regular expressions" (RegExp, RegEx) sind Zeichenketten, die eine Art Filterkriterium für Texte darstellen. So ist es möglich mit einem regulären Ausdruck einen Text zu beschreiben, der mit einem "A" beginnt, nur aus Buchstaben besteht und mit einem Punkt endet. Einsätze sind dabei vor allem komplizierte Suchausdrücke, aber auch Ersetzungen, indem man die zu suchenden Textteile als reguläre Ausdrücke definiert. Reguläre Ausdrücke sind in vielen Programmiersprachen gang und gäbe.
Beispiel
#include <regex> #include <iostream> #include <string> using namespace std; int main () { char const* text = "Ein langer Text\tmit Trennzeichen; Tja"; smatch matches; //typedef für match_results<string::const_iterator> //Splitte Text nach Trennzeichen if (regex_search(text, matches, regex("[ ,.\\t\\n;:]")) { for (auto match: matches) //decltype(match) -> std::sub_match<std::string::iterator> bzw. std::ssub_match { cout << match << '\n'; } } }
Erläuterung
Ein regulärer Ausdruck kann auf verschiedene Art und Weisen implementiert sein. Die Standardbibliothek liefert die Möglichkeit, zu testen, welche Art von regulären Ausdrücken sie unterstützt, dieses Feature wurde im Beispiel der Einfachheit halber jedoch nicht verwendet. Die Klassestd::regex
bzw.std::basic_regex<>
dient dafür, die Engine, die im Hintergrund Strings parst, zu abstrahieren. Mithilfe verschiedener Standardfunktionen wiestd::regex_search
oderstd::regex_replace
lässt sich so mit regulären Ausdrücken arbeiten.Ein eigener Container, der Matches für reguläre Expressions aufnehmen kann, ist dabei
std::match_results
, bzw. die fürstd::string
explizit spezialisierte Versionstd::smatch
. Dastd::match_results
einen Container darstellt, ist es möglich, mittels Iteratoren einzelne Matches (std::sub_match
bzw. fürstd::strings
std::ssub_match
) auszulesen. Dieses ist im Prinzip einfach vonstd::pair<Iterator, Iterator>
abgeleitet, besitzt aber einen Konvertierungsoperator nachstd::string
.Ein weiteres Feature von regulären Ausdrücken in C++09 ist, dass es auch mit den C++-Locales zusammenarbeitet. Eine eigene Regex-Traits-Klasse (
std::regex_traits
), die für einzelne reguläre Ausdrücke individualisiert werden kann, sorgt dabei für die Anbindung an bestimmte Locales – dabei ist anzumerken, dass eigene Spezialisierungen vonstd::regex_traits
auch non-C++-Locales verwenden können. Regex-Traits erlaubt außerdem, die Syntax von regulären Ausdrücken den eigenen Gewohnheiten anzupassen.Alles in allem stellt die C++09-RegEx-Bibliothek ein mächtiges Werkzeug dar, um mit regulären Ausdrücken auch international arbeiten zu können.
Status
siehe 1.1Unterstützung
Die C++09-RegEx-Bibliothek wird, obwohl sie bereits im TR1 enthalten war, meines Wissens noch von keinem Compiler unterstützt.Proposals
N1429 - Reguläre Ausdrücke
N1836 - Der Draft des TR11.6 Zufallszahlen
Der TR1 definiert ein mächtiges Interface, um Zufallszahlen zu erzeugen. Er definiert dabei sogenannte "Engines", die Zufallszahlen nach bewährten Algorithmen erzeugen und stellt diverse Verteilungen zur Verfügung, die mit diesen Engines arbeiten können.
Beispiel
#include <random> using namespace std; int main () { //Zufallszahlen-Seeder: Erwartet in dem Fall Eingaben von std::cin seed_seq seeder {istream_iterator<int>(cin), istream_iterator<int>()}; mt19937 mersenne_twister_engine {seeder}; ranlux24 discard_block_engine {seeder}; knuth_b shuffle_order_engine {seeder}; bernoulli_distribution distribution_a {0.25}; uniform_int_distribution<int> distribution_b {0, 99}; weibull_distribution<double> distribution_c; bool a = distribution_a (discard_block_engine); int b = distribution_b (shuffle_order_engine); double c = distribution_c (mersenne_twister_engine); }
Status
siehe 1.1Unterstützung
Die tatsächlichen Bezeichnungen weichen zum Teil vom Vorschlag im TR1 ab. Außerdem wird meines Wissens nach die Zufallszahlen-Bibliothek von C++09 noch von keinem Compiler zu 100% unterstützt.Proposals
N1836 - Der Draft des TR1
N2111 - Die Zufallszahlen-Bibliothek2 Multithreading
C++09 bietet den langersehnten Multithreading-Support. Dies geschieht einerseits über die Multithreading-Bibliothek, die Objekte für Threads, Mutexe und Locks und Monitore (Condition Variables) bereitstellt.
2.1 Threads
In Zeiten, in denen Prozessoren mehrere Kerne haben, ist es sinnvoll, Berechnungen auf diese zu verteilen. Auch Programme auf Single-Core-Prozessoren können durch den Einsatz von Threads verbessert werden, indem einzelne Komponenten des Programms voneinander entkoppelt jeweils den eigenen Anforderungen entsprechend Systemzeit verbrauchen können.
Beispiel 1 - Neue Threads erstellen
#include <thread> #include <functional> #include <algorithm> #include <iostream> #include <iterator> #include <vector> void bar () { } struct MyThread { void start (int data) { //An den nächsten Thread weitergeben this_thread::yield(); } }; int main () { //Starte neuen Thread mit Aufruf von bar(); std::thread thread_a {bar}; MyThread instance; //Starte neuen Thread mit Aufruf von instance.start(42); std::thread thread_b {std::mem_fun(&MyThread::start), &instance, 42}; std::vector<int> data; //Kopiere in einem neuen Thread Daten von cin in den vector std::thread reader ( [&]() ( std::copy(std::istream_iterator<int>(std::cin), std::istream_iterator<int>(), std::back_inserter(data)) ) ); //Warte auf den Eingabe-Thread reader.join(); //Gib aus, was die Hardware an Parallelität verträgt std::cout << std::thread::hardware_concurrency() << std::endl; }
Erläuterung
Dastd::thread
mithilfe von Variadic Templates implementiert ist, können beliebige Funktionen und Funktionsobjekte als Eingangspunkte für einen Thread definiert werden. Sowohl freistehende Funktionen wiebar
als auch Elementfunktionen wieMyThread::start
und sogar Lambda-Funktionsobjekte (siehe dazu den ersten Teil der Reihe) können als eigener Thread ausgeführt werden. Das Interface vonstd::thread
erinnert ein bisschen an die entsprechende Boost-Bibliothek.Beispiel 2 - Einmalige Initialisierung
#include <thread> #include <array> std::once_flag init_once; void general_init_routine (int data) { std::cout << "Führe allgemeine Initialisierungsaufgaben (Singletons?) durch\n"; } void thread_fun (int x) { std::call_once (init_once, general_init_routine, x); //general_init_routine wurde mit Sicherheit ausgeführt und beendet. } int main () { array<thread, 3> threads = { thread {thread_fun, 1}, thread {thread_fun, 2}, thread {thread_fun, 3} }; //Warte auf alle Threads for (auto thread: threads) thread.join(); }
Erläuterung
In diesem Beispiel werden drei Threads erstellt, die jeweils versuchen, eine gemeinsame Initialisierungsfunktion aufzurufen. Mithilfe vonstd::once_flag
undstd::call_once
ist sichergestellt, dass diese Funktion tatsächlich nur ein einziges Mal aufgerufen wird. Alle anderen Threads müssen mit ihrer Ausführung außerdem warten, bis diese Funktion auch tatsächlich aufgerufen wurde. In diesem Beispiel könntegeneral_init_routine
also entweder mit 1, 2 oder 3 aufgerufen werden.Status
Die Multithreading-Bibliothek ist Teil des aktuellen Working-Drafts.Unterstützung
Die Boost-Bibliothek Boost.Threads ist ähnlich aufgebaut, jedoch nicht vollständig kompatibel mit der C++09-Multithreading-Bibliothek. Dastd::thread
sehr stark auf neue Sprachfeatures baut, und diese nur von wenigen Compilern (in der Form nur von GCC 4.3) unterstützt werden, ist derzeit keine Implementierung verfügbar.Proposals
N2139 - Hintergründe
N2410 - Thread-Safety in der Standardbibliothek
N2427 - Atomare Operationen
N2480 - Eine informelle Erklärung des neuen C++-Speichermodells
N2497 - Die Multithreading-Bibliothek2.2 Mutexe und Monitore
Mutexe und Monitore dienen der sicheren Verwendung von Daten zwischen mehreren Threads. Sie verhindern Wettlaufsituationen, das sind Situationen, in denen das Ergebnis einer Operation vom zeitlichen Verhalten bestimmter Einzelsituationen abhängt. Ein sicherer Umgang mit ihnen verhindert außerdem Deadlocks, das sind Situationen, in denen Threads auf Informationen warten, die sie jedoch erst durch einen jeweils anderen Thread bekommen würden. Da dieser andere Thread jedoch selbst auf Informationen wartet (zyklische Abhängigkeit), stoppt das Programm.
Beispiel 1 - Mutexe und Locks
#include <thread> #include <mutex> #include <iostream> #include <array> #include <string> std::mutex input_mutex, output_mutex; void thread_fun () { for (;;) { std::string txt; { std::lock_guard<std::mutex> lock {input_mutex}; std::cin >> txt; } { std::lock_guard<std::mutex> lock {output_mutex}; std::cout << txt << '\n'; } } } int main () { std::array<std::thread, 2> threads = { std::thread {thread_fun}, std::thread {thread_fun} }; for (auto thread: threads) thread.join(); }
Erläuterung
Mutexe ermöglichen die sichere Verwendung von Objekten, auf die mehrere Threads gleichzeitig zugreifen wollen. Dazu bieten sie die Funktionenlock
,try_lock
undunlock
an. Eine bestimmte Art von Mutexen,std::timed_mutex
undstd::recursive_timed_mutex
bieten zusätzlich die Möglichkeit, die Ressource nur für eine bestimmte Zeitspanne zu blockieren.
Das automatische Sperren und entsperren eines Mutex (RAII) geschieht mit Hilfe der beiden Lock-Klassenstd::lock_guard
undstd::unique_lock
, wobeistd::unique_lock
am ehesten demboost::scoped_locked
entspricht.
Mit den Standard-Funktionenstd::try_lock
undstd::lock
ist es zudem möglich, eine beliebige Anzahl von Locks auf eine sichere Weise gleichzeitig zu sperren.Beispiel 2 - Monitore
#include <thread> #include <mutex> #include <condition_variable> #include <queue> #include <iostream> std::mutex guard; std::condition_variable monitor; std::queue<int> data; void produce_data () { for (;;) { int x; std::cin >> x; //Threadsicheres Speichern der neuen Daten std::lock_guard<std::mutex> lock {guard}; data.push_back(x); //Signalisiere, dass neue Daten da sind if (data.size()) monitor.notify_one(); } } void consume_data () { for (;;) { int x; { //Versuche, Daten zu lesen std::unique_lock<std::mutex> lock {guard}; //Warte, bis neue Daten ankommen monitor.wait(lock, [&]()(!data.empty())); //Daten sind verfügbar x = data.front(); data.pop(); } std::cout << "Eingabe: " << x << std::endl; } } int main () { std::thread producer {produce_data}; std::thread consumer {consume_data}; producer.join(); consumer.join(); }
Erläuterung
Der Namecondition_variable
rührt daher, dass eine bestimmte Bedingung erfüllt sein muss, bis ein Thread seine Arbeit wieder aufnimmt: Diese Bedingung stellt in obigem Beispiel die Lambda-Funktion[&]()(!data.empty())
dar. Der aktuelle Thread soll also solange warten, bisdata.empty()
nicht mehrtrue
liefert, also solange bis neue Daten eingetroffen sind. Diese Benachrichtigung erhält er durch den anderen Thread (monitor.notify_one()
).
wait
entsperrt gleichzeitig den Lock, so dass der Producer-Thread diequeue
mit neuen Daten füllen kann. Sobald die Bedingung fürwait
erfüllt ist, wird der Lock allerdings wieder hergestellt.Status
Siehe 2.1Unterstützung
Siehe 2.1Proposals
N2406 - Hintergründe zu Mutexen, Locks und Monitoren3 Datum und Zeit
Da die C++-Multithreading-Bibliothek auch Funktionen wie Sleep und zeitabhängiges Warten auf Ressourcen ermöglichen soll, musste für sie eine eigene Date-Time-Bibliothek geschaffen werden.
Die meisten Funktionen sind bereits in der Boost Date-Time-Bibliothek implementiert. Die Date-Time-Bibliothek, die in C++09 inkludiert werden soll, sieht sich dabei als Vorgängerin für eine noch umfassendere Bibliothek im Technical Report 2.Beispiel 1 - Date-Time in Verbindung mit Threads
#include <date_time> #include <thread> template <class Lock> void thread (Lock lock) { std::this_thread::sleep(std::seconds(1)); std::recursive_timed_mutex rtm; rtm.time_lock(std::milliseconds(20)); }
Erläuterung
Die Date-Time-Bibliothek führt drei Arten von Typen ein:- Zeitpunkte. Ein Zeitpunkt repräsentiert einen Moment im Zeitkontinuum.
- Dauer. Eine Dauer ist nicht von einem Zeitpunkt abhängig und repräsentiert eine gewisse Zeitlänge. Diese kann auch negativ sein.
- Uhren. Ein Uhren-Typ ist ein Interface zu einem Gerät, das einen Zeitpunkt zurückgibt.
Die konkreten Typen in C++09 sollen sein:
- utc_time - ein Zeitpunkt in einer Auflösung von Nanosekunden.
- hiresolution_clock - Eine Uhr, die die momentane utc_time zurückgibt.
- hours, minutes, seconds, milliseconds, microseconds und nanoseconds - Typen, die eine Zeitdauer repräsentieren.
Mit diesen Typen kann man intuitiv arbeiten.
Beispiel 2 - Intuitives Interface
#include <date_time> using namespace std; int main () { utc_time now = hiresolution_clock::universal_time(); utc_time tomorrow = now + hours(24); hours aday {24}; aday += minutes {10};//Kompilierfehler: Verlust an Genauigkeit minutes m {10}; m += aday; //OK, Stunden können in Minuten umgerechnet werden }
Erläuterung
Ein Objekt vom Typutc_time
, das von seinem Standardkonstruktor initialisiert wird, repräsentiert die UTC-Zeit 1970-01-01 00:00:00.000000000.Status
Die Date-Time-Library wurde gleichzeitig mit der Multithreading-Bibliothek in den Working-Draft aufgenommen. Allerdings wurde sie mittlerweile wieder aus dem Working-Draft entfernt, da zu viele Details nicht geklärt werden konnten. Eine Erklärung für ihr Verschwinden findet sich hier.Unterstützung
Mir sind keine Compiler bekannt, die diese Features unterstützen.Proposals
N2411 - Date/Time für C++0x
N2492 - Date/Time als Teil der Multithreading-Bibliothek
N2498 - Custom Time Duration Support4 System-Fehler
Vor allem im Hinblick auf die Dateisystem-Bibliothek, die mit dem Technical Report 2 eingeführt werden soll, ist es nötig, Fehlermeldungen des Systems in C++-Ausnahmen zu verwenden. Aber auch die Multithreading-Bibliothek profitiert davon, System-Fehlermeldungen über eine standardisierte Schnittstelle zu präsentieren. Der Standard stützt sich dabei auf die POSIX-Fehlerflags.
*Beispiel 1 - Verwendung von
system-error
*#include <system_error> #include <thread> #include <iostream> void foo () {} int main () { try { std::thread thr { foo }; } //Wenn der Thread nicht gestartet werden konnte... catch (std::system_error& error) { std::cerr << error.what(); } }
Beispiel 2 - Eigene Erweiterungen
#include <system_error> namespace std { struct my_error_catalog : virtual public std::error_catalog { typedef std::error_catalog base_type; static value_type const out_of_ink = 20001; static value_type const out_of_paper = 20002; virtual auto is_valid_value (value_type v) const throw () -> bool { if (!base_type::is_valid_value(v)) return v == out_of_ink || v == out_of_paper; return true; } virtual auto str (value_type v) const throw () -> char const* { char const* ink = "Out of Ink"; char const* paper = "Out of Paper"; if (base_type::is_valid_value(v)) return base_type::str(v); switch (v) { case out_of_ink: return ink; case out_of_paper: return paper; default: return nullptr; } } virtual auto last_value() const throw() -> value_type const { return out_of_paper; } } } //... if (printer.ink_level() < 5percent) { throw system_error { std::my_error_catalog::out_of_ink, my_error_catalog() }; }
Erläuterung
Eine der Stärken der System-Error Schnittstelle ist es,std::locales
zu unterstützen. Die Vorgabe lautet außerdem, dass die Nachrichten, diewhat
liefert, eine Fehler-Diagnose erleichtern sollen.Status
Dieses Feature ist Teil des Working-Drafts. Allerdings gibt es bereits Vorschläge, die Schnittstelle zu modifizieren bzw. zu erweitern. So wie es aussieht sind auch hier einige Unklarheiten aufgetreten, und es existiert bereits der Vorschlag,system_error
wieder aus dem Working-Draft zu entfernen, wie bereits mit der Date/Time-Bibliothek geschehen.Unterstützung
Mir sind keine Compiler bekannt, die dieses Feature unterstützen.Proposals
N2303 - System-Error-Unterstützung
N2538 - Entfernen der System-Error-Unterstützung5 Integration neuer Sprachfeatures
Durch die vielen neuen Möglichkeiten, die C++09 bietet – von Variadic Templates, Rvalue-Referenzen bis zur Unicode-Unterstützung – war es ebenso nötig, bereits bestehende Bibliotheken dem Wandel der Sprache anzupassen. Neben der Einführung von Konzepten und Concept-Maps für die Klassen der STL und der Implementierung von Move-Semantik durch Rvalue-Referenzen, die weitgehend unsichtbar im Hintergrund arbeitet, gibt es auch einige durchaus sichtbare und erwähnenswerte Features, die das Arbeiten mit der "alten" Standardbibliothek erleichtern sollen.
5.1 Standard-Concepts
Um das Rad nicht ständig neu erfinden zu müssen, definiert die Standard-Bibliothek einige Konzepte wie LessThanComparable, die häufig gebraucht werden. Waren es einst bestimmte Bedingungen, die nur formal an Konzepte wie Iteratoren, Container usw. gestellt wurden, sind diese nun auch explizit im Code formulierbar.
Beispiel
#include <concepts> struct MyOrder { int i; }; bool operator == (MyOrder const& a, MyOrder const& b) { return a.i == b.i; } bool operator < (MyOrder const& a, MyOrder const& b) { return a.i < b.i; } template <typename T> requires EqualityComparable<T> && LessThanComparable<T> void foo (T a, T b) { a == b; a != b; //automatisch generiert - Definition in EqualityComparable a >= b; //automatisch generiert - Definition in LessThanComparable } struct MyInteger { long long value; MyInteger (long long v) : value(v) {} MyInteger& operator+= (MyInteger x) { value += x.value; return *this; } //... }; template <Arithmetic T> void bar () { T a{1}, b{2}, c{3}; a = b + c; //operator+ wird automatisch erzeugt. }
Status
Daconcepts
selbst noch nicht im Working-Draft enthalten sind, ist auch der Header<concepts>
noch nicht mit dabei. Bis zur Veröffentlichung des neuen Standards wird sich dies jedoch mit Sicherheit noch ändern.Unterstützung
ConceptGCC unterstützt die<concepts>
-Bibliothek. Allerdings kann ConceptGCC noch nicht sogenannte "Default-Implementierungen" ausconcepts
erstellen. (D.h. die obigen Beispiele funktionieren nicht.)Proposals
N2036 - Hintergründe
N2037 - Concepts für die Standardbibliothek (Einführung)
N2572 - Die wichtigsten Concepts5.2 Noch mehr: std::string
Die Funktionalität rund um
std::string
wird noch weiter ausgebaut, um ihn endgültig zur Standard-Stringklasse für C++ zu machen und von den lästigen nullterminierten Char-Zeigern wegzukommen.*Beispiel 1 - Schnittstellen für
std::string
*#include <locale> #include <fstream> #include <string> int main () { std::string loc { "fr_FR.UTF-8" }; std::locale myLocale { loc }; //Statt loc.c_str() std::string filename { "foo.txt" }; std::fstream file { filename };//Dito hier file.close(); //Auch filebufs unterstützen jetzt std::strings file.rdbuf()->open(filename, ios::out); }
Erläuterung
Mit diesem Feature wird das Interface der Standardbibliothek vereinheitlicht und anfängerfreundlicher gestaltet.*Beispiel 2 -
operator<<
*#include <string> int main () { std::string a, b { "strings" }; a << "Aneinander" << 'r' << "eihen von " << b; }
Erläuterung
Das Konkatenieren von Strings wird somit übersichtlicher als die Alternative mitoperator+=
. Allerdings wirdoperator<<
nicht die Konversion von anderen Typen nachstd::string
ermöglichen.Beispiel 3 - neue Stringfunktionen
#include <stdexcept> #include <iostream> #include <string> int main () { std::string number; cin >> number; try { long twice = std::stol(number) * 2; double half = std::stod(number) / 2; string output = "Zweimal " + number + " ist " + to_string (twice) + ", die Hälfte davon ist " + to_string(half); } catch (std::invalid_argument const&) { //Konnte number nicht in long oder double konvertieren } catch (std::out_of_range const&) { //Number war zu groß/klein für long oder double } }
Erläuterung
Mit den Standardfunktionenstd::stoi
,std::stol
,std::stoul
,std::stoll
,std::stoull
,std::stof
,std::stod
,std::stold
undstd::to_string
wird endlich "von Zahl nach String und zurück" ohne Stringstreams ermöglicht.Status
Die neuen Schnittstellen fürstd::strings
sind bereits im Working-Draft enthalten. Andere Erweiterungen, die von Neuerungen in der Sprache selbst abhängen, werden frühestens dann inkludiert, wenn das entsprechende Feature in den Working-Draft kommt.operator<<
ist leider noch nicht im Working-Draft und es ist nicht sicher, ob er es noch rechtzeitig in den Standard schafft. Die numerischen Konvertierungsfunktionen sind bereits Teil des Working-Drafts.Proposals
N1981 - Einheitliche Verwendung vonstd::string
N2233 -basic_string operator<<
6 Erweiterungen für den TR2 und danach
Während noch fleißig am kommenden Standard gearbeitet wird, lässt sich auch schon ein bisschen was über den Technical Report 2 sagen: Unterstützung für Dateisystem-Operationen wird definitiv darin enthalten sein. Anderes, wie eine Netzwerkbibliothek, befindet sich noch in einer sehr frühen Planungsphase, auch wenn das Komitee den Neuerungen gegenüber recht aufgeschlossen ist. Das liegt wohl auch hauptsächlich daran, dass die meiste Arbeitszeit nun in den kommenden Standard fließt und nicht in spekulative Erweiterungen danach.
6.1 Dateisystem
Die Dateisystem-Bibliothek, die in den Technical-Report 2 aufgenommen werden soll, orientiert sich stark an Boost.Filesystem.
Beispiel
#include <iostream> #include <filesystem> using std::tr2::sys; using std::cout; int main (int argc, char** argv) { std::string p { argc <= 1 ? "." : argv[1] }; if (is_directory(p)) { for (directory_iterator iter(p); iter != directory_iterator(); ++iter) { cout << iter->path().leaf() << ' '; //Dateinamen anzeigen if (is_regular(iter->status())) cout << file_size(iter->path()); cout << '\n'; } } else cout << (exists(p) ? "Found: " : "Not Found: ") << p << '\n'; }
Status
Dieses Feature ist bereits als Teil des TR2 angenommen worden. Der TR2 steht allerdings noch weit vor der Veröffentlichung.Unterstützung
Mir sind keine Compiler bekannt, die dieses Feature unterstützen. Allerdings funktioniert Boost.Filesystem ähnlich.Proposals
N1975 - Quelle und Erläuterung des Beispiels6.2 Weitere mögliche Neuerungen
Über weitere Bibliotheken, die in den TR2 aufgenommen werden sollen, lassen sich im Moment nur Vermutungen aufstellen. Möglicherweise wird die Boost.Asio-Bibliothek für C++-Netzwerkfähigkeiten zuständig sein, Boost.Any könnte ebenso aufgenommen werden.
Sollten Ihnen als LeserInnen Bibliotheken einfallen, die Sie für besonders aufnahmenswert empfinden, folgen Sie dem "Call for Proposals" - Wer weiß, vielleicht kommt die eine oder andere Idee auch durch :).
Proposals
N2175 - Proposal für eine Netzwerkbibliothek
N1393 - Any Library Proposal
N2044 - Shared Memory Proposal
N2276 - Thread Pools und Futures7 Ausblick
In den folgenden Artikeln dieser Reihe beschäftige ich mich eingehender mit bestimmten Features, zu denen ich nun einen groben Überblick gegeben habe. Die Auswahl, nach der ich vorgehe, ist dabei einfach:
Bereits verfügbare Features zuerst, bevorzugt Änderungen, die einen großen Einfluss auf den zukünftigen Programmierstil haben werden.
Die Wahl fällt damit zunächst auf Variadic Templates, weil diese das nächste große Feature sind, das bereits implementiert ist und weil es wahrscheinlich ist, dass es in naher Zukunft von mehreren Compilern unterstützt wird, da es nicht sonderlich schwer zu implementieren ist.
8 Verweise
ISO - C++ Library Working Group (LWG) Status Report
Implementierungsstatus der libstdc++
C++0X - The New Face of Standard C++
Daraus: Minimal Garbage Collection Support - The Latest Proposal (I-III)
N2151 - Variadic Templates für die Standardbibliothek
-
Sehr schön.
Und vielen Dank für die ganze Mühe.
-
Interessant,
Danke!
-
Schöner Artikel
Für die System-Fehler (4.) hat boost 1.35 bereits eine Implementierung: http://www.boost.org/doc/libs/1_35_0/libs/system/doc/index.html
-
also erstmal spitzen artikel und vielen dank für die viele mühe, die du dir mit deinen artikeln gibst.
eine frage zu funktionsobjekten. in deinem code steht ein bsp zum vertauschen der parameter einer funktion:
function<void (int)> foo_reversed = bind (foo_binary, placeholders::_2, placeholders::_1);
müsste das nicht
function<void (int, int)> foo_reversed = bind (foo_binary, placeholders::_2, placeholders::_1);
heißen, da die neue funktion zwei int als parameter nimmt?
btw. könntest du bitte noch einen hinweis einfügen, wie sich das mit den placeholders gestaltet? beim ersten lesen habe ich eine ganze weile gestutzt, was du da gemacht hast.
-
ja, du hast da natürlich recht. ich habe auch eine etwas ausführlichere erklärung eingefügt.
-
edit: sorry ganz vergessen: vielen dank für die korrekturen und vor allem bin dankbar, dafür dasss ich dir hier nochmal ausdrücklich für deine mühe danken werde. ich hätte keine zeit gehabt, mich durch den vollständigen draft zu lesen, aber deine mühe erspart mir diese tätigkeit. es hat mich sich selten ein artikel wie deine hier interessiert. du bist ein brückenbauer aka pontifex.
da ich jetzt gerade den draft zu dem placeholders las. der spruch "M is the implementation-defined number of placeholders" ist irgendwie ein bisschen blöd. ich hoffe einfach mal, dass jeder compilerbauer m hinreichend groß ansetzt.
wenn ich jetzt gerade nichts überlas, ist nämlich keine möglichkeit vorgesehen, um m abzufragen. das wäre extrem lästig.
-
danke
-
ghorst schrieb:
da ich jetzt gerade den draft zu dem placeholders las. der spruch "M is the implementation-defined number of placeholders" ist irgendwie ein bisschen blöd. ich hoffe einfach mal, dass jeder compilerbauer m hinreichend groß ansetzt.
wenn ich jetzt gerade nichts überlas, ist nämlich keine möglichkeit vorgesehen, um m abzufragen. das wäre extrem lästig.Im Anhang steht dazu folgendes:
2 The number of distinct placeholders ([3.2]) is implementation defined. The number should be at least 10.
10 Stück sollten normalerweise völlig ausreichen.
-
Beim Juni-Meeting wurde außerdem noch eine Bibliothek für Zeitpunkte und Zeitdauern eingeführt
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2661.htm