[X] 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 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. Mutexte 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 üebrgeben, 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 durch std::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 etwa make_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 Implementation des TR1.

    Proposals
    N1403 - Tuple
    N1450 - Smart-Pointer
    N1453 - Reference-Wrapper
    N1836 - Der Draft des TR1

    1.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 von std::bind1st und std::bind2nd , Argumente jeweils nur an den ersten oder zweiten Parameter binden zu können zu Gunsten von std::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 Standardfunktionen std::bind1st und std::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:

    void foo_binary (int, int);
    function<void (int)> foo_unary = bind (foo_binary, 42, placeholders::_1);
    
    foo_unary (23);
    //gleichbedeutend mit:
    foo_binary (42, 23);
    
    function<void (int)> foo_reversed = bind (foo_binary, placeholders::_2, placeholders::_1);
    
    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 momenten 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.1

    Unterstützung
    siehe 1.1
    Closures und damit auch std::reference_closure werden allerdings noch von keinem Compiler unterstützt.

    Proposals
    N1402 - Polymorphe Funktionsobjekt-Wrapper
    N1836 - Der Draft des TR1

    1.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öße
    • std::unordered_set , die gehashte Version von std::set
    • std::unordered_map , die gehashte Version von std::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 und std::unordered_set lassen sich - vom Interface her - ebenso verwenden wie die Standardcontainer std::map und std::set . Statt einen Vergleich mit dem Kleiner-als-Operator durchzuführen (standardmäßiges Verhalten bei std::map und std::set ), wird hier eine Hash-Funktion verwendet. Um mit benutzerdefinierten Typen arbeiten zu können, muss daher eine Spezialisierung von std::hash (Header <hash> ) implementiert werden.
    std::forward_list ist eine einfach verkettete Liste, im Gegensatz zu std::list , die doppelt verkettet ist.

    Status
    siehe 1.1

    Unterstützung
    siehe 1.1

    Proposals
    N1456 - Hashtables
    N1548 - Array
    N1836 - Der Draft des TR1
    N2545 - Singly-linked list

    1.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 Reieh) abgelöst werden. Diese bieten ein eleganteres Interface, um Template-Parametern gewisse Constraints aufzuerlegen.

    Status
    siehe 1.1
    Im TR1 allerdings nicht enthalten sind std::enable_if , std::conditional und std::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 Namensbereich std::tr1 . Als Alternative lassen sich bisweilen die entsprechenden Type-Traits aus der Boost-Bibliothek verwenden. Für std::decay gibt es allerdings meines Wissens nach noch keine Implementierung.

    Proposals
    N1424 - Type Traits
    N1836 - Der Draft des TR1
    N2240 - std::enable_if und std::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 auch 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 Klasse std::regex bzw. std::basic_regex<> dient dafür, die Engine, die im Hintergrund Strings parst, zu abstrahieren. Mithilfe verschiedener Standardfunktionen wie std::regex_search oder std::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ür std::string explizit spezialisierte Version std::smatch . Da std::match_results einen Container darstellt, ist es möglich, mittels Iteratoren einzelne Matches ( std::sub_match bzw. für std::strings std::ssub_match ) auszulesen. Dieses ist im Prinzip einfach von std::pair<Iterator, Iterator> abgeleitet, besitzt aber einen Konvertierungsoperator nach std::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 von std::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.1

    Unterstü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 TR1

    1.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.1

    Unterstü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-Bibliothek

    2 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
    Da std::thread mithilfe von Variadic Templates implementiert ist, können beliebige Funktionen und Funktionsobjekte als Eingangspunkte für einen Thread definiert werden. Sowohl freistehende Funktionen wie bar als auch Elementfunktionen wie MyThread::start und sogar Lambda-Funktionsobjekte (siehe dazu den ersten Teil der Reihe) können als eigener Thread ausgeführt werden. Das Interface von std::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 von std::once_flag und std::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önnte general_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. Da std::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 Implementation 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-Bibliothek

    2.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
    Mutex ermöglichen die sichere Verwendung von Objekten, auf die mehrere Threads gleichzeitig zugreifen wollen. Dazu bieten sie die Funktionen lock , try_lock und unlock an. Eine bestimmte Art von Mutexen, std::timed_mutex und std::recursive_timed_mutex bieten zusätzlich die Möglichkeit, die Resource nur für eine bestimmte Zeitspanne zu blockieren.
    Das automatische Sperren und entsperren eines Mutex (RAII) geschieht mit Hilfe der beiden Lock-Klassen std::lock_guard und std::unique_lock , wobei std::unique_lock am ehesten dem boost::scoped_locked entspricht.
    Mit den Standard-Funktionen std::try_lock und std::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 Name condition_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, bis data.empty() nicht mehr true 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 die queue mit neuen Daten füllen kann. Sobald die Bedingung für wait erfüllt ist, wird der Lock allerdings wieder hergestellt.

    Status
    Siehe 2.1.

    Unterstützung
    Siehe 2.1.

    Proposals
    N2406 - Hintergründe zu Mutexen, Locks und Monitoren

    3 Datum und Zeit

    Da die C++-Multithreading-Bibliothek auch Funktionen wie Sleep und zeitabhängiges Warten auf Resourcen ermöglichen soll, musste für sie eine eigene Date-Time-Biblothek 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, dass 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 Typ utc_time , dass 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 Support

    4 System-Fehler

    Vor allem im Hinblick auf die Dateisystem-Bibliothek, die mit dem Technical Report 2 eingeführt werden soll, ist es nötige, 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. Auch die Multithreading-Bibliothek soll Systemfehler über diese Schnittstelle an die Anwendung weiterleiten.

    *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, die what 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ützung

    5 Integration neuer Sprachfeatures

    Durch die vielen neuen Möglichkeiten, die C++09 bietet - von Variadic Templates, Rvalue-Rereferenzen 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
    Da Concepts 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 keine sogenannte "Default-Implementationen" aus concepts erstellen. (D.h. die obigen Beispiele funktionieren nicht.)

    Proposals
    N2036 - Hintergründe
    N2037 - Concepts für die Standardbibliothek (Einführung)
    N2572 - Die wichtigsten Concepts

    5.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 mit operator+= . Allerdings wird operator<< nicht die Konversion von anderen Typen nach std::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 Standardfunktionen std::stoi , std::stol , std::stoul , std::stoll , std::stoull , std::stof , std::stod , std::stold und std::to_string wird endlich "von Zahl nach String und zurück" ohne Stringstreams ermöglicht.

    Status
    Die neuen Schnittstellen für std::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 von std::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

    5.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 Beispiels

    5.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 Futures

    7 Ausblick

    In den folgenden Artikeln dieser Reihe beschäftige ich mich eingehender mit bestimmten Features, zu denen ich nun einen groben Überblick gegeben habe. Das erste große Feature werden. Die Auswahl, nach der ich vorgehe, ist dabei einfach:

    Bereits verfügbare Features zuerst, bevorzugt Änderungen, die einen großen Einfluss auf 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



  • so, ich habe das hier jetzt auch noch ein bisschen erweitert. falls noch wer wünsche hat, dort:

    http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2433.html

    sind alle proposals für die standardbibliothek zu finden. (auch sachen, die erst im TR2 oder danach kommen könnten)



  • in der Regel die Header mit dem Präfix "tr1/" versehen werden

    Die ganzen Templates usw. des TR1 sind nicht im tr1-Unterverzeichnis. Sie stecken ganz normal in bekannten Headern, die es schon im std-Namespace gibt. Smartpointre sind z.B. nicht in <tr1/memory> sondern in <memory>. Steht ja auch im TR1-Draft so drin. Wird auch von Boosts TR1 und von Dinkumware so umgesetzt:
    http://www.dinkumware.com/manuals/?manual=compleat&page=memory.html
    http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2005/n1745.pdf (hier habe ich nichts von <tr1/...> gefunden.)
    Ob die TR1-Implementierung physikalisch woanders liegt, spielt für den Benutzer keine Rolle, ist ein Implementierungsdetail. Inkludieren tut man keine Headers aus einem Unterverzeichnis.



  • hm die tatsächliche regelung ist anscheinend ein kompromiss:
    http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2005/n1836.pdf
    1.3:

    TR1/Last Revision schrieb:

    3 Even when an extension is specified as additions to standard headers (the third category in section 1.2), vendors should not simply add declarations to standard headers in a way that would be visible to users by default. [Note: That would fail to be standard conforming, because the new names, even within a namespace, could conflict with user macros. —end note] Users should be required to take explicit action to have access to library extensions.

    4 It is recommended either that additional declarations in standard headers be protected with a macro that is not defined by default, or else that all extended headers, including both new headers and parallel versions of standard headers with nonstandard declarations, be placed in a separate directory that is not part of the default search path.

    wir natürlich im artikel nachgebessert.



  • So, endlich habe ich den Teil zu Threads und den (kleinen) Rest fertiggestellt.
    Wenn noch jemand Extra-Wünsche hat, hier ist die liste mit allen vorschlägen, die im laufe der letzten jahre beim komitee eingetrudelt sind.

    ansonsten bitte ich einmal die multithreading-spezialisten, sich durchzusehen, ob ich auch halbwegs nachvollziehbar geschrieben habe.



  • Sieht schon gut aus. Hast du schon mal auf den Counter für den ersten Teil angesehen? http://magazin.c-plusplus.net/counter Ist wirklich nicht schlecht für den ersten Tag.

    Smart Pointer. Sie dienen automatischem Management von dynamischem Speicher.

    Wer Smart-Pointers schon kennt der weiß was gemeint ist, die anderen aber nicht. Wie wäre es mit:

    Smart Pointer. Zeiger die sich selbst um die Freigabe des Speichers kümmern.
    

    Tuple. Eine Art Container, der jedoch Daten auch verschiedenen Typs beinhalten kann.

    Hier würde ich noch erwähnen, dass sie eine konstante Länge haben.

    auto p = make_shared<int>(42); //Entspricht new int(42)
    

    da würde ich im Kommentar schreiben:

    new int(42) für Smart Pointer
    

    Bei deiner Version könnte man sich fragen wieso man nicht einfach new int(42) nimmt.

    std::tuple ist im Prinzip einfach eine generischere Version von std::pair,

    Generisch ist auch std::pair. Du meinst beliebig lang.

    der auch Elementfunktionen beinhalten kann

    Was ist eine Elementfunktionen? Meinst du einen Methodenzeiger?

    zu gunsten einer generischeren Variante, std::bind, aufgegeben.

    Gleiche Bemerkung zu "generischeren" wie weiter oben.

    std::unordered_map und std::unordered_set lassen sich - vom Interface her - ebenso verwenden wie die Standardcontainer std::map und std::set.

    Nur, dass sie anstatt eines op< eine hash Funktion erwarten und -wie der Name schon suggeriert- sind ihre Elemente nicht geordnet.

    void start (int data)
    {
    //An den nächsten Thread weitergeben
    this_thread::yield();
    }

    Was weiter geben?

    Ist das mem_fun da notwendig?

    //Gib aus, was die Hardware an Parallelität verträgt

    Du meinst wie viele Prozessoren es gibt?

    Da std::thread mithilfe von Variadic Templates implementiert ist, können beliebige Funktionen und Funktionsobjekte als Eingangspunkte für einen Thread definiert werden.

    Das geht auch mit einfachen Templates. Du musst schon sagen wo die Variadic Templates hier mitmischen.

    //general_init_routine wurde mit Sicherheit ausgeführt.

    Und beendet!

    Der aktuelle Thread soll also solange warten, bis data.empty() nicht mehr true liefert, also solange bis neue Daten eingetroffen sind.

    Wie oft überprüft er die Bedingung oder geht das irgendwie anders?



  • sorry, werde schauen, dass ich nächste woche zeit hab, die änderungen einzuarbeiten. bei mir geht gerade alles drunter und drüber ^^'

    Ben04 schrieb:

    std::tuple ist im Prinzip einfach eine generischere Version von std::pair,

    Generisch ist auch std::pair. Du meinst beliebig lang.

    hm ich dachte in etwa so: std::pair ist generisch, aber std::tuple ist noch viel generischer - weil es eben noch viel allgemeiner implementiert werden kann.
    bevor hier ein streit darüber entfacht, was genau "generisch" bedeutet, werde ich wohl versuchen, das phänomen mit mehr worten zu beschreiben als "generisch" (auch wenn es IMHO passt 😉 )

    Was ist eine Elementfunktionen? Meinst du einen Methodenzeiger?

    da es keine referenzen auf methoden gibt, klar. wird aber trotzdem verständlicher formuliert.

    std::unordered_map und std::unordered_set lassen sich - vom Interface her - ebenso verwenden wie die Standardcontainer std::map und std::set.

    Nur, dass sie anstatt eines op< eine hash Funktion erwarten und -wie der Name schon suggeriert- sind ihre Elemente nicht geordnet.

    ich dachte mir, falls ich da ins detail gehe, müsste ich std::hash erwähnen. soll ich?

    auf den rest gehe ich mal ein, wenn ich mehr zeit habe 🙂
    aber danke für die außerordentlich konstruktive kritik.



  • queer_boy schrieb:

    ich dachte mir, falls ich da ins detail gehe, müsste ich std::hash erwähnen. soll ich?

    Ich finde, dass man bei einer Hashtabelle die Hashfunktion erwähnen muss, denn ohne die geht da nix.



  • Folgender Link ist vielleicht ganz interessant: Implementierungsstatus C++200x der libstdc++



  • so, nun da ich endlich wieder etwas zeitlichen freiraum habe (und vor allem die nötige motivation gefasst), hab ich die Bens vorschläge eingearbeitet und gleich noch alles auf den neuesten stand gebracht (auch den anderen artikel).

    wenn jetzt noch jemand vorschläge/wünsche hat, bitte äußern. ansonsten fehlen nur mehr die quellen zu den entsprechenden proposals und vielleicht ein paar weiterführende links.

    btw. das beispiel für die RegExs habe ich - vereinfacht - aus der englischen wikipedia übernommen. im zuge der recherche bin ich allerdings drauf gekommen, dass die ziemlichen stumpfsinn geschrieben haben. naja, ist überarbeitet.



  • post scriptum:
    ich habe jetzt übrigens auch endlich den ersten teil der serie in den wikipedia artikel zu C++ integriert. ich frage mich, ob es nicht vielleicht sinnvoll wäre, unter anderem um die bekanntheit des magazins zu fördern, auch andere artikel dort zu verlinken. z.b. scheint shades artikel über exceptions ganz gut zum entsprechenden wikipedia-artikel zu passen.



  • ready for cor[R]ections 🙂



  • So, ich bin glücklicherweise noch zur Korrektur gekommen; hoffe, dass ich in der Eile nichts übersehen habe.
    Bezüglich Wikipedia: ich wollte den Artikel auch schon mal verlinken, aber damals war noch ne Vollsperrung über "C++" verhängt. Tja, und jetzt hat wohl wieder jemand den Link rausgelöscht. –.–
    Begründung: „Der Überblick ist nicht umfassend. Verlässlichkeit des angegebenen Links ist auch unklar.“ Das Argument „nicht umfassend“ dürfte sich mit Erscheinen dieses Artikels erledigt haben, und viel verlässlicher als nach jedem Abschnitt die Quellen anzugeben geht ja wohl auch nicht.
    Deshalb wäre eine erneute Verlinkung auf beide Teile imho gerechtfertigt.
    Super Artikel übrigens. 👍
    —————

    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 durch std::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 etwa make_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 TR1

    1.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 von std::bind1st und std::bind2nd , Argumente jeweils nur an den ersten oder zweiten Parameter binden zu können zu Gunsten von std::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 Standardfunktionen std::bind1st und std::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:

    void foo_binary (int, int);
    function<void (int)> foo_unary = bind (foo_binary, 42, placeholders::_1);
    
    foo_unary (23);
    //gleichbedeutend mit:
    foo_binary (42, 23);
    
    function<void (int)> foo_reversed = bind (foo_binary, placeholders::_2, placeholders::_1);
    
    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.1

    Unterstützung
    siehe 1.1
    Closures und damit auch std::reference_closure werden allerdings noch von keinem Compiler unterstützt.

    Proposals
    N1402 - Polymorphe Funktionsobjekt-Wrapper
    N1836 - Der Draft des TR1

    1.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öße
    • std::unordered_set , die gehashte Version von std::set
    • std::unordered_map , die gehashte Version von std::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 und std::unordered_set lassen sich – vom Interface her – ebenso verwenden wie die Standardcontainer std::map und std::set . Statt einen Vergleich mit dem Kleiner-als-Operator durchzuführen (standardmäßiges Verhalten bei std::map und std::set ), wird hier eine Hash-Funktion verwendet. Um mit benutzerdefinierten Typen arbeiten zu können, muss daher eine Spezialisierung von std::hash (Header <hash> ) implementiert werden.
    std::forward_list ist eine einfach verkettete Liste, im Gegensatz zu std::list , die doppelt verkettet ist.

    Status
    siehe 1.1

    Unterstützung
    siehe 1.1

    Proposals
    N1456 - Hashtables
    N1548 - Array
    N1836 - Der Draft des TR1
    N2545 - Singly-linked list

    1.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 sind std::enable_if , std::conditional und std::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 Namensbereich std::tr1 . Als Alternative lassen sich bisweilen die entsprechenden Type-Traits aus der Boost-Bibliothek verwenden. Für std::decay gibt es allerdings meines Wissens nach noch keine Implementierung.

    Proposals
    N1424 - Type Traits
    N1836 - Der Draft des TR1
    N2240 - std::enable_if und std::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 Klasse std::regex bzw. std::basic_regex<> dient dafür, die Engine, die im Hintergrund Strings parst, zu abstrahieren. Mithilfe verschiedener Standardfunktionen wie std::regex_search oder std::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ür std::string explizit spezialisierte Version std::smatch . Da std::match_results einen Container darstellt, ist es möglich, mittels Iteratoren einzelne Matches ( std::sub_match bzw. für std::strings std::ssub_match ) auszulesen. Dieses ist im Prinzip einfach von std::pair<Iterator, Iterator> abgeleitet, besitzt aber einen Konvertierungsoperator nach std::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 von std::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.1

    Unterstü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 TR1

    1.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.1

    Unterstü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-Bibliothek

    2 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
    Da std::thread mithilfe von Variadic Templates implementiert ist, können beliebige Funktionen und Funktionsobjekte als Eingangspunkte für einen Thread definiert werden. Sowohl freistehende Funktionen wie bar als auch Elementfunktionen wie MyThread::start und sogar Lambda-Funktionsobjekte (siehe dazu den ersten Teil der Reihe) können als eigener Thread ausgeführt werden. Das Interface von std::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 von std::once_flag und std::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önnte general_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. Da std::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-Bibliothek

    2.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 Funktionen lock , try_lock und unlock an. Eine bestimmte Art von Mutexen, std::timed_mutex und std::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-Klassen std::lock_guard und std::unique_lock , wobei std::unique_lock am ehesten dem boost::scoped_locked entspricht.
    Mit den Standard-Funktionen std::try_lock und std::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 Name condition_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, bis data.empty() nicht mehr true 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 die queue mit neuen Daten füllen kann. Sobald die Bedingung für wait erfüllt ist, wird der Lock allerdings wieder hergestellt.

    Status
    Siehe 2.1

    Unterstützung
    Siehe 2.1

    Proposals
    N2406 - Hintergründe zu Mutexen, Locks und Monitoren

    3 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 Typ utc_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 Support

    4 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, die what 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ützung

    5 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
    Da concepts 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" aus concepts erstellen. (D.h. die obigen Beispiele funktionieren nicht.)

    Proposals
    N2036 - Hintergründe
    N2037 - Concepts für die Standardbibliothek (Einführung)
    N2572 - Die wichtigsten Concepts

    5.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 mit operator+= . Allerdings wird operator<< nicht die Konversion von anderen Typen nach std::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 Standardfunktionen std::stoi , std::stol , std::stoul , std::stoll , std::stoull , std::stof , std::stod , std::stold und std::to_string wird endlich "von Zahl nach String und zurück" ohne Stringstreams ermöglicht.

    Status
    Die neuen Schnittstellen für std::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 von std::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 Beispiels

    6.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 Futures

    7 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



  • michba schrieb:

    So, ich bin glücklicherweise noch zur Korrektur gekommen; hoffe, dass ich in der Eile nichts übersehen habe.

    super 👍 , gut dass das noch rechtzeitig geschafft ist; wenn ich richtig sehe ist der artikel dann der einzige für juni?

    Bezüglich Wikipedia: ich wollte den Artikel auch schon mal verlinken, aber damals war noch ne Vollsperrung über "C++" verhängt. Tja, und jetzt hat wohl wieder jemand den Link rausgelöscht. –.–
    Begründung: „Der Überblick ist nicht umfassend. Verlässlichkeit des angegebenen Links ist auch unklar.“ Das Argument „nicht umfassend“ dürfte sich mit Erscheinen dieses Artikels erledigt haben, und viel verlässlicher als nach jedem Abschnitt die Quellen anzugeben geht ja wohl auch nicht.
    Deshalb wäre eine erneute Verlinkung auf beide Teile imho gerechtfertigt.
    Super Artikel übrigens. 👍

    tatsächlich wieder rausgelöscht... also was noch umfassender sein soll, weiß ich auch nicht. ein link auf den working draft? super informativ 🙄
    und mit verlässlichkeit wahrscheinlich gemeint, dass meine wahre identität wohl ein geheimnis ist. tja...



  • mann, jetzt sehe ich mir doch tatsächlich nochmal den artikel zu C++ in der wikipedia genauer an, und ich glaube es kaum: der typ, der den link rausgelöscht hat, fügt ja absichtlich falsche sachen ein und beruft sich dabei noch auf eine (veraltete) quelle... 😡



  • wie ich gerade gehört habe, soll der neue standard nach samstag feature-complete werden (es läuft gerade ein meeting in frankreich) - womöglich muss ich also die beiden artikel in nächster zeit nochmal überarbeiten. meine frage nun: soll ich die änderungen einfach selbst einfügen, auch wenn der artikel schon veröffentlicht wurde, oder soll ich die artikel hier dann nochmal posten, um sie korrektur lesen zu lassen? (so wie's aussieht müsste da z.b. noch ein großer teil zu futures dazukommen) - oder einfach hier in einem neuen thread die jeweiligen änderungen posten, bevor ich sie in den veröffentlichten artikeln eintrage?



  • Also ich sehe da drei Möglichkeiten:
    1. Du machst weiteren Teil, wenn sich das neue mit dem alten nicht wirklich überschneidet.
    2. Du ergänzt den Artikel hier und er wird korrekturgelesen und dann nochmal veröffentlicht - der alte verschwindet dann. (Macht das unser Portal mit?)
    3. Du wartest mit der Veröffentlichung bis alles eingearbeitet ist.

    Ich werde mit der heutigen Veröffentlichung möglichst lange warten, damit ich deine Entscheidung berücksichtigen kann. Dein Artikel ist ja dieses Mal der einzige.



  • queer_boy schrieb:

    mann, jetzt sehe ich mir doch tatsächlich nochmal den artikel zu C++ in der wikipedia genauer an, und ich glaube es kaum: der typ, der den link rausgelöscht hat, fügt ja absichtlich falsche sachen ein und beruft sich dabei noch auf eine (veraltete) quelle... 😡

    Bin gespannt was die auf deine Fragen in der Diskussion antworten. Du bist absolut im recht und hast zwei wirklich tolle Artikel geschrieben 👍 Das werden nun wohl meine neuen Lieblingsartikel!

    MfG SideWinder



  • estartu schrieb:

    Also ich sehe da drei Möglichkeiten:
    1. Du machst weiteren Teil, wenn sich das neue mit dem alten nicht wirklich überschneidet.
    2. Du ergänzt den Artikel hier und er wird korrekturgelesen und dann nochmal veröffentlicht - der alte verschwindet dann. (Macht das unser Portal mit?)
    3. Du wartest mit der Veröffentlichung bis alles eingearbeitet ist.

    Ich werde mit der heutigen Veröffentlichung möglichst lange warten, damit ich deine Entscheidung berücksichtigen kann. Dein Artikel ist ja dieses Mal der einzige.

    hm zu 1. - ich glaube nicht, dass sich noch so viel überraschendes ergeben wird. concepts werden jetzt wohl fix aufgenommen werden; das einzige, was ich da ändern müsste, wäre der eine satz unter "status" (concepts sind jetzt teil des working drafts); sowie "der working draft ist feature complete" oä. sogar Date/Time könnte, wenn keine zeit für das komitee ist, wieder im originalwortlaut aufgenommen werden (🙄 )
    zu 3. - das ginge vielleicht noch mit diesem artikel, aber nicht mit dem der schon draußen ist. da ist es mir lieber, wenn ich dann einfach hier die größeren ergänzungen/überarbeitungen poste und der artikel dann neu veröffentlicht wird (aber wieso gleich so drastisch? wir könnten doch auch einfach diesen [X]-thread hier lassen und sobald ich was überarbeitet habe, poste ich; wenn dann korrekturgelesen ist, wird einfach hinübergepastet - oder ist das auch nicht gut für das portal?)
    vor allem denke ich, dass es gut ist, den artikel einmal zu veröffentlichen. bis die post-meeting mails rauskommen dürfte es ja wohl auch noch einige zeit lang dauern.



  • Ich denke, es ist auch kein Problem, wenn du einfach die entsprechenden Abschnitte im Original-Artikel, d.h. im Subforum „Die Artikel“, editierst (wenn das die Forumsberechtigungen zulassen) und mir hier dann mitteilst, welche Abschnitte das sind.

    Bei deiner guten Rechtschreibung (das wenige, was ich korrigierte, waren fast nur Tippfehler) ist das denke ich vertretbar. 🙂

    Edit: den Artikel würde ich auf jeden Fall heute veröffentlichen.


Log in to reply