Cpp

  •  

    Basiswissen zu Templates:

    nach C++ Templates von David Vandevoorde und Nicolai M. Josuttis

     

    Funktionstemplates:

    Beispiel:

    template <typename T>

    inline T const& max_(T const& a, T const& b){

    return a < b ? b : a;


    }

    #include <iostream>
    #include <string>

    using namespace std;

    int main(){

    cout << "max_(3,4): " << max_(3,4) << endl;

    cout << "max_(3.5,3.6): " << max_(3.5,3.6) << endl;

    cout << "max_(string(\"abc\"), string(\"cdf\") : " << max_(string("abc"), string("cdf") ) << endl;
    cout << "max_(\"abc\",\"cdf\") : " << max_("abc","cdf") << endl;

    //cout << "max_( 1, 2.2 ): " << max_(1, 2.2) << endl;
    cout << "max_< double > (1,2.2): " << max_< double > (1,2.2) << endl;

    cout << "max_(static_cast< double > (1),2.2): " << max_(static_cast < double >(1),2.2) << endl;
    cout << "max_( new int, new int ): " << max_( new int, new int ) << endl;


    }
    • die Funktionenfamilie wird durch den Typ T bestimmt
    • template <typename T> ist äquivalent zu template <class T>
    • jeder Typ kann verwendet werden, der den Operator < unterstützt
    • die Ersetzung des Templateparameters T durch ein Templatetype wird als Templateinstanzierung bezeichnet
    • für jeden konkreten Typ (double) wird die Templatefunktion zu:
    inline double const& max_(double const& a, double const& b){ 

    return a < b ? b : a;
    • Templates werden zwei Mal auf syntaktische Richtigkeit geprüft
      • der Templatecode im Allgemeinen
      • die Templateinstanzierung im Besonderen (kann der Typ als Templateparameter verwendet werden)
      • Konsequenzen:
        • eine Templateinstanzierung kann nur erfolgen, wenn sie ihre Templatedefinition kennt
        • das klassische Trennung von Übersetzen und Linken ist aufgehoben, denn "klassisch" genügt zum Übersetzen die Funktionsdeklaration
        • Templates müssen im Header definiert werden

    Templateparameter und Aufrufparameter :

    • T in template < typename T > wird als Templateparameter bezeichnet
    • a und b in max_( T const& a, T const& b ) nennt man Aufrufparameter "call parameters"
    • Funktionstemplates kennen im Gegensatz zu Klassentemplates keine Defaultparameter, da sich, historisch bedingt, Templateparameter aus den Aufrufargumenten ableiten lassen sollten right max_( T const& = int ) ist noch nicht möglich )
    • Lösung des Codeproblems mittels zwei Templateparametern:
    template <typename T1, typename T2>

    inline T1 max_(T1 const& a, T2 const& b){

    return a < b ? b : a;

    }
    • Nachteile:
      • max_( 42, 66.6 ) = 66 =! 66.6 = max_( 66.6, 42 )
      • der zweite Aufrufargument muss gegenfalls konvertiert werden right lokales temporäres Objekt geht "out of scope" beim Verlassen der max_ Funktion right Rückgabe durch kopieren
    • das Konzept zur Parameterbestimmung: Aufrufargumente right Templateparameter right Aufrufparameter
    • Versuch mit drei Templateparametern:
    template <typename T1, typename T2, typename RT>

    inline RT max_(T1 const& a, T2 const& b){

    return a < b ? b : a;

    }
    • Nachteil: der Aufruf ist mühsam max_<int,double,double>(4,4.2)
    • Umstellung der Templateparameter:
    template <typename RT, typename T1, typename T2>

    inline RT max_(T1 const& a, T2 const& b){

    return a < b ? b : a;

    }
    • Vorteil: kann mittels max_<double>(4,4.2) instanziert werden, da die Aufrufargumente die restlichen Templateparameter und Aufrufparameter bestimmen

    Template Funktionen überladen:

    • Überladen bezeichnet die Möglichkeit, mehrere Funktionen mit gleichem Namen aber unterschiedlichen Aufrufparametern zu definieren, sodass der Compiler entscheiden muss, welche Funktion er aufzurufen hat
    • die Aufrufparameter werden auch gern als Signatur einer Funktion bezeichnet
    • ein paar Beispiele:
    // max von zwei ints (1)
    inline int const& max_(int const& a, int const& b){

    return a < b ? b : a;

    }

    // max von zwei beliebigen, gleichen Typen (2)
    template <typename T>
    inline T const& max_(T const& a, T const& b)

    {
    return a < b ? b : a;
    }

    // max von drei beliebigen Typen (3)
    template <typename T>
    inline T const& max_(T const& a, T const& b, T const& c)

    {
    return max_(max_(a,b), c);

    }

    int main()
    {
    max_(7, 42, 68);
    // 3 mit int

    max_(7.0, 42.0);
    // 2 mit double
    max_('a', 'b');
    // 2 mit char

    max_(7, 42);
    // 1 mit int
    max_<>(7, 42);

    // 2 mit int
    max_<double>(7, 42);
    // 2 mit double
    max_('a', 42.7);
    // 1 mit int

    }
    • Funktionen können mit Funktionstemplates koexistieren
    • Funktionstemplates unterstützen keine automatische Typekonvertierung im Gegensatz zu Funktionen (max('a',42.7 ist nur mittels einer Funktion möglich)
    • "rule of thumb" zur Funktionenauswahl: die am meisten spezialisierte Funktion wird ausgewählt
      • Funktionen werden Funktionstemplates vorgezogen ( max_(7, 42) => 1 )
      • wenn das Funktionstemplate besser passt als die Funktion, wird das Funktionstemplate vorgezogen (max_('a','b')) right 2 nicht 1, da keine Typekonvertierung notwendig
    • durch günstige Auswahl der Aufrufparameter, kann die Anzahl der Templateinstanziierungen reduziert werden:
    // max von zwei Pointern auf den gleichen Typ
    template <typename T>

    inline T* const& max(T* const& a, T* const& b){

    return *a < *b ? b : a;

    }

    Klassentemplates

    • Container aus der Standard Template Library (STL) sind typische Beispiele für Klassentemplates

    Beispiel:

    template <typename T>
    class Stack {
    private:

    std::vector<T> elems;

    public:
    Stack();
    Stack( Stack<T> const& );
    Stack>T>& operator= (Stack<T> const&);

    void push(T const&);
    void pop();
    T top() const;
    bool empty() const;

    };
    • die Stackmethoden werden dann wie Funktionstemplates definiert right die Regeln für Funktionstemplates gelten natürlich auch hier;
    template <typename T>

    bool Stack<T>::empty() const{ ... }
    • Instanzierung der Memberfunktionen:
      • die Memberfunktionen der Klassentemplates werden dann instanziert, wenn sie verwendet werden:
    template <typename T>

    class Foo{
    public:
    T t;
    void set( const T& val ){ t= val.t; }

    T operator*( const& T val){ return t*val.t ; }

    };

    int main(){
    Foo< std::string > fooString;

    Foo< std::string > barString;
    fooString.set(std::string("foo");

    barString.set(std::string("bar");
    // die nächste Zeile führt zum Compilefehler
    Foo < std::string> fooBarString= fooString*barString;

    }
    • Vorteile:
      • schnellers Übersetzen
      • weniger Code wird instanziert
      • Templateklassen können mit Typen T instanziert werden, die nicht alle Methoden des Klassentemplates unterstützen
      Nachteile:
      • Fehler zur Laufzeit des Programms

    Spezialisierung von Templateklassen:

    • ähnlich zu Funktionstemplates kann man auch Klassentemplates für bestimmte Typen spezialisieren
      • Klassentemplates spezialisieren right nun müssen auch alle Memberfunktionen spezialisiert werden
      • Memberfunktionen spezialisieren right nun kann die Templateklasse als ganzes nicht mehr spezialiert werden
    • Beispiel zur Spezialisierung der Klassentemplates
    template <typename T > 
    class Stack{

    ...
    };
    template <typename T>
    T Stack<T>::top() const {

    ...
    }
    • wird für die Spezialisierung std::string
    template <>

    class Stack<std::string>{
    ...
    };
    T Stack<std::string>::top(){

    ...
    }
    • Beispiel zur Spezialisierung der Memberfunktionen für std::string:
    template <typename T > 

    class Stack{

    T top() const;

    template <>

    T top<std::string>() const;

    };

    template <typename T>

    T Stack<T>::top const{
    ...
    }

    template <typename T>

    template <>
    T Stack<T>::top<std::string>() const{
    ...

    }

     

    Partielle Spezialisierung:

    • Template Parameter können im Gegensatz zum vorherigen Punkt teilweise spezialisiert werden:
    • allgemeine Template Parameter (1):
    template <typename T1, typename T2>
    class MyClass{

    ...
    };

     

    • gleiche Template Parameter (2):

    template <typename T>

    class MyClass<T,T>{
    ...
    };
    • zweiter Template Parameter ist int (3):
    template <typename T>
    class MyClass<T,int>{

    ...
    };
    • beide Templateparameter sind Pointer (4):
    template <typename T1, typename T2>

    class MyClass<T1*,T2*>{
    ...
    };
    • Anwendung:
    MyClass<int,float> a; // (1)
    MyClass<float,float> a; // (2)

    MyClass<float,int> a; // (3)
    MyClass<int*,float*> a; // (4)

    MyClass<int,int>; // Fehler, da nicht eindeutig (2) versus (3)
    MyClass<int*,int*>; // Fehler, da nicht eindeutig (2) versus (4)
    • mögliche Auflösung der Fehler:
    template <>
    class MyClass<int,int>{

    ...
    };
    template <typename T>
    class MyClass<T*,T*>{

    ...
    };
    • partielle Spezialisierung ist ein mächtiges Werkzeug für neue Programmiertechniken: Static Metaprogramming und Expression Templates
    // berechne 3^N

    template<int N>
    class Pow3 {
    public:
    enum { result = 3 * Pow3<N-1>::result };

    };

    template<>
    class Pow3<0> {
    public:

    enum { result = 1 };
    };

    int main()
    {
    std::cout << "Pow3<7>::result = " << Pow3<7>::result << '\n';

    }
    • Static Metaprogramming heißt diese Technik, weil zur Compilzeit (static) Code erzeugt und evaluiert wird (Metaprogramming)
    • auch Werte können Templateparameter sein
    • es gilt: Pow3<7>::result = 3 * Pow3<6>::result = 3 * 3 * Pow3<6>::result =... = 3 * 3 * 3 * 3 * 3 * 3 * 3 * Pow3<0> = 3 * 3 * 3 * 3 * 3 * 3 * 3 * 1
    • Expression Templates stammen aus der Matrixarithmtik
    • so kann mittels Expression Templates ein Matrixoperation so geschickt aufgesplittet werden, daß folgende Operation statt 6000 Lese- und 4000 Schreiboperatone nur noch 2000 Lese- und 1000 Schreiboperationen benötigt
    Array<double> x(1000),y(1000);

    x= 1.2*x + x*y;
    • beide Techniken werden gerade in neueren C++ Bibliotheken (Blitz++, Boost) exzessiv verwendet

    Default Template Argumente:

    • im Gegensatz zu Funktionstemplate kennen Klassentemplates default Argumente
    • Prominentes Beispiel:
    template<class Ch, class Tr= char_traits<Ch>, class A= allocator<Ch> >

    class std::basic_string{
    ...
    };
    typedef basic_string<char> string;

    typedef basic_string<wchar_t> wstring;
    int main(){
    std::string str; // entspricht std::basic_string<char> str

    basic_string<char,myCharTrait, myCharAllocator> myString;
    }
    • Flexibilität wird hier durch das Traits-Pattern erreicht

    "Nontype" Template Parameter:

    • Template Parameter können auch gewöhnliche Werte sein
    • die Templateklasse wird dann nicht mit einem Typ, sondern mit einem Wert initialisiert

    Templateklassen Parameter:

    • Beispiel:
    template <typename T, int MAXSIZE>

    class Stack {
    private:
    T elems[MAXSIZE];
    int numElems;

    public:
    Stack();
    void push(T const&);

    void pop();
    T top() const;
    bool empty() const {

    return numElems == 0;
    }
    bool full() const {

    return numElems == MAXSIZE;
    }
    };
    ...

    int main(){

    Stack<int,20> int20Stack;
    Stack<int,40> int40Stack;

    Stack<std::string,40> stringStack;
    }
    • jede Template Instanzierung stellt einen eigenen Typ dar
    • d.h.: keine implizite cast int20Stack=int40Stack oder explizite cast int20Stack= static_cast< int20Stack>(int40Stack) sind möglich
    • default Wert sind auch zulässig:
    template <typename T = int, int MAXSIZE = 200>

    class Stack{
    ...
    };

    Funktionstemplate Parameter:

    • typisches Beispiel in der STL:
    template <typename T, int VAL>

    T addValue(T const& x){
    return x + VAL;

    }
    ...
    int main(){
    ...
    std::transform( source.begin(), source.end() ,

    dest.begin(),
    addValue<int,5>);
    };

    Einschränkungen:

    • Floating-point Zahlen, Klassentypen und Objekte mit interne Bindung sind als Templateparameter nicht erlaubt:
    template <double VAT>

    double process( double v){
    return v * VAT;

    }
    template <std::string name >
    class MyClass{
    ...
    };

    template <char const* name>
    class MyClass{
    ...
    };
    • Floating-point Zahlen werden wohl im nächsten Standard unterstützt werden
    • Objekte mit interner Bindung (lokale Objekte) wie String Literale, die erst zur Laufzeit bekannt sind, können nicht zur Compilezeit zum Instanziieren verwendet werden
  •  

    Bridge Pattern

    Zweck

    Das Bridge Pattern dient dazu, das Interface von der Implementierung zu trennen.
    Dies bietet in folgenden Situationen Vorteile:
    • wenn die Implementierung zur Laufzeit ausgetauscht werden soll
    • sowohl das Interface als auch die Implementierung durch Unterklassenbildung erweiterbar bleiben soll
    • der Austausch der Implementierung kein neues Übersetzen des Codes zur Folge haben soll

    Auch bekannt als

    • handle/body

    Struktur


    Bridge Pattern
    • Das Brücken Muster besteht im wesentlichen aus drei Komponenten:
      1. einer Interfaceabstraktion, gegen die der Client programmiert ist
      2. einer Implementationsabstraktion, die von den konkreten Implementierungsklassen umgesetzt werden müssen
      3. den Implentationsklassen
    • Durch eine spezialisierte Interface Abstraktion kann das Pattern noch zusätzlich angepaßt werden.
    • Die Interface- und Implementierungsabstraktion unterstützen in der Regel nicht das gleiche Interface, sonst würden man das Pattern eher als Proxy bezeichnen.

    Konsequenzen

    • dynamisch, zur Laufzeit
      • Entkopplung von der Schnittstelle und der Implementierung
        1. der Klient kann zur Laufzeit die Schnittstelle austauschen
        2. die Schnittstelle kann zur Laufzeit ihre Implementierung austauschen
        3. die Implementierung kann von mehreren Schnittstellen genützt werden
        4. MOVED TO... maximale Flexibilität zur Laufzeit, da sowohl die Abstraktion über der Schnittstellle als auch Abstraktion über der Implementierung ausgetauscht werden können
    • statisch
      • Verbesserte Erweiterbarkeit durch die Interface- und Implemtierungsabstraktion
    • Verstecken von Implementierunsdetails vor dem Clienten, so daß zusätzliche Logik ( Referenzzähler ) in die Implementierung gesteckt werden kann ( vgl. Proxy ; dort wird das Interface um zusätzliche Funktionalität erweitert )

    Implementierung

    1. nur eine Implementierungsklasse
      • im degenerierten Fall, indem nur eine Implementierung vorhanden ist, ist es nicht notwendig eine Implementierungsabstraktion zu unterstützen
    2. Erzeugen des Implementierungsobjekts
      • um die konkreten Implementationsklassen zu erzeugen, auf denen die Schnittstelle arbeitet, bietet es sich an eine abstrakte Fabrik bzw. Fabrikmethode einzuführen
    3. Gemeinsame Nutzung von Implementierungsobjekten
      • im handle/body Idiom von James Coplien enthält der Body - die Implementierung - einen Zähler, der vom Handle - dem Interface - genutzt wird
    4. Mehrfachvererbung
      • die Implementierung und das Interface könnte man auch statisch, durch Vererbung zusammenführen werden, indem man privat von der Implementierung und öffentlich von dem Interface erbt
      • durch diesen Ansatz geht einerseits die ganze Flexibilität zur Laufzeit und andererseits der Name verloren

    Beispiel

    • Als Beispiel der Orginalcode zum GOF Buch von 1994.
      BridgeExample.gif
    • Die Klasse Window beschreibt die Interfaceabstraktion und hält sich einen Zeiger auf die Fensterimplementierung.
    class Window {
    public:

    Window(View* contents);

    // requests handled by window
    virtual void DrawContents();

    virtual void Open();
    virtual void Close();

    virtual void Iconify();
    virtual void Deiconify();

    // requests forwarded to implementation
    virtual void SetOrigin(const Point& at);

    virtual void SetExtent(const Point& extent);
    virtual void Raise();

    virtual void Lower();

    virtual void DrawLine(const Point&, const Point&);

    virtual void DrawRect(const Point&, const Point&);

    virtual void DrawPolygon(const Point[], int n);

    virtual void DrawText(const char*, const Point&);



    protected:
    WindowImp* GetWindowImp();
    View* GetView();



    private:
    WindowImp* _imp;
    View* _contents; // the window's contents

    }
    • Als Gegenstück und reine Interfacebeschreibung der Implementierung dient die Klasse WindowImpl.
    • Zu dieser Zeit nannte man solche rein virtuellen Klassen gerne Protokollklassen.
    class WindowImp {

    public:
    virtual void ImpTop() = 0;
    virtual void ImpBottom() = 0;

    virtual void ImpSetExtent(const Point&) = 0;

    virtual void ImpSetOrigin(const Point&) = 0;



    virtual void DeviceRect(Coord, Coord, Coord, Coord) = 0;

    virtual void DeviceText(const char*, Coord, Coord) = 0;

    virtual void DeviceBitmap(const char*, Coord, Coord) = 0;

    // lots more functions for drawing on windows...
    protected:
    WindowImp();
    }
    • Die verschiedene Arten von Window werden durch die Interfacespezialisierungen - ApplicationWindwow, IconWindow -definiert.
    class ApplicationWindow : public Window {
    public:

    //...
    virtual void DrawContents();
    };

    void ApplicationWindow::DrawContents() {

    GetView()->DrawOn(this);
    }


    class IconWindow : public Window {

    public:
    //...
    virtual void DrawContents();
    private:

    const char* _bitmapName;
    }
    • Ruft nun der Client eine Operation auf Window auf, so fungiert Window als Stellvertreter, der seine WindowImp Referenz nutzt um die Funktionalität zu unterstützen.
    void Window::DrawRect(const Point& p1, const Point& p2) {

    WindowImp* imp = GetWindowImp();
    imp->DeviceRect(p1.X(), p1.Y(), p2.X(), p2.Y());

    • Abhängig von der referenzierten WindowImpl wird die Funktionalität auf den konkreten Windowimplementierung angesprochen.
    void XWindowImp::DeviceRect(

    Coord x0, Coord y0, Coord x1, Coord y1
    ) {
    int x = round(min(x0, x1));

    int y = round(min(y0, y1));
    int w = round(abs(x0 - x1));

    int h = round(abs(y0 - y1));
    XDrawRectangle(_dpy, _winid, _gc, x, y, w, h);

    }


    void PMWindowImp::DeviceRect(
    Coord x0, Coord y0, Coord x1, Coord y1

    ) {
    Coord left = min(x0, x1);
    Coord right = max(x0, x1);

    Coord bottom = min(y0, y1);
    Coord top = max(y0, y1);



    PPOINTL point[4];


    point[0].x = left; point[0].y = top;

    point[1].x = right; point[1].y = top;

    point[2].x = right; point[2].y = bottom;

    point[3].x = left; point[3].y = bottom;



    if (
    (GpiBeginPath(_hps, 1L) == false) ||

    (GpiSetCurrentPosition(_hps, &point[3]) == false) ||

    (GpiPolyLine(_hps, 4L, point) == GPI_ERROR) ||

    (GpiEndPath(_hps) == false)
    ) {

    // report error


    } else {
    GpiStrokePath(_hps, 1L, 0L);

    }
    • Die entscheidende Frage ist nun, wie kommt die Window Abstraktion an ihre Implementierung?
    • In diesem konkreten Beispiel wird sie durch eine abstrakte Fabrik, die als Singleton implementiert ist, erzeugt.

    WindowImp* Window::GetWindowImp() {
    if (_imp == 0) {

    _imp = WindowSystemFactory::Instance()->MakeWindowImp();
    }
    return _imp;

    }

     

    Verwandte Pattern

    • Proxy
    • Adapter
    • Fassade

    Variationen

    Das Bridge Pattern ist insbesondere im C++ Umfeld sehr beliebt. Hilft es doch die Abhängigkeit von Interface und Implementierung aufzulösen, so daß die Compilezeiten deutlich kürzer werden. James Coplien war ein treibende Kraft bei der Entwicklung des Bridge Pattern, auch bekannt als Handle/Body Idiom und seinen Variationen, die er sehr ausführlich in dem Buch Advanced C+++ - Programming Styles and Idioms - beschreibt.

    Envelope/Letter Idiom


    Envelop - Letter Idiom
    • der wesentliche Unterschied zwischen dem Handle/Body Idiom und dem Envelope/Letter Idiom ist es, daß der Handle auf eine anderen Typ verweist während der Envelope einen Untertyp referenziert
    • der Envelope als Basisklasse umschließt den Letter als Unterklasse
    • dies kann man schön am String als Handle bzw. der Number als Envelope festmachen
    class Number {
    private:
    Number* rep;
    ...
    };
    • die Mächtigkeit/Abstraktion dieses Idioms sieht kann man an der Anweisungsfolge
    Number a = Number(1.0, 1.0); // (1)

    Number b = 2.0; // (2)
    Number c = a + b; // (3)
    schön nachvollziehen
    • die Ausdrücke (1) und (2) führen zu folgenden Konstuktoraufrufen, die explizit einen virtuellen Konstruktor nutzen

    Number::Number(double d, double e) {

    rep = new Complex(d, e);
    }

    Number::Number(double d) {
    rep = new RealNumber(d);

    }
    • der Client arbeitet nur noch auf der Abstraktion Number, daß seine Datentypen die Addition (3) unterstützen
    • im Gegensatz hierzu würden klassische OO-Implementierung dies Problem wohl in folgender Form lösen
    MyNumber* a = new MyComplex(1.0, 1.0); 

    MyDouble* b = new MyDouble(2.0);
    MyComplex* c = a + b;
    • ALERT! einerseits muß man hier auf Indirektionen ( Pointer oder Referenz ) und andererseits auf den konkreten Zahlentypen arbeiten

    Counted Pointer Idiom

    Diese weitere Variante des Bridge Patterns, das wieder auf James Coplien zurückgeht, wird in dem Klassiker Patternorientierte Softwarearchitektur beschrieben.
    • Um die Speicherverwaltung von dynamisch, mehrfach referenzierten Objekten in C++ zu ermöglichen, stellt der Body eine Referenzähler zur Verfügung, der vom Handle aktualisiert wird.
    • damit ist es möglich, daß
      1. mehrere Clienten dasselbe Objekt referenzieren.
      2. Referenzen auf gelöschte Objekte vermieden werden
      3. Objekte, die nicht mehr benötigt werden, automatisch gelöscht werden.

    Beispiel

    Anwendung
    • die Handles sollen das gleiche Objekt referenzieren
    Handle h(...);
    {
    Handle g(h);

    h->TueEtwas();
    g->TueEtwas();
    }
    h->TueEtwas();
    • g geht out of scope, wird daher automatisch gelöscht
    • h bleibt gültig MOVED TO... der Body von g wurde nicht gelöscht
    Body
    • um die Zugriffkontrolle zu kapseln, enthält dieser einerseits einen privaten Kon - und Destruktor und andererseits eine Referenzzähler
    • in Leben gerufen kann er nur durch seinen Freund Handle, der gleichzeitig den vom Body angeboten refCounter verwaltet
    class Body{
    public:

    void TueEtwas();
    private:
    friend class Handle;

    Body();
    ~Body();
    int refCounter;
    };
    Handle
    • der Handle erzeugt den Body und setzte den Referenzzähler auf 1
    class Handle{

    public:
    Handle( ...){
    body= newBody(...);

    body->refCounter=1;
    }
    • im Kopierkonstruktor wird der Body zugewiesen und der Referenzzähler erhöht
      Handle( const Handle& h){
    body= h.body;

    body->refCounter++;
    }

     

    • im Zuweisungsoperator wird der Zähler des zugewiesens Body erhöht, der Body zugewiesen und er ursprüngliche Body gegebenfalls gelöscht

    Handle& operator= ( const Handle& h ){

    h.body->refCounter++;
    if ( --body->refCounter) <= 0 ) delete body;

    body= h.body;
    return *this;
    }
    • die Aufgabe des Destruktors ist es über die Lebenszeit des Body zu wachen
      ~Body(){
    if ( --body->refCounter) <= 0 ) delete body;

    }
    • die Proxy Eigenschaft des Handles sieht man am -> Operator
      Body* operator->() { return body; }
    • um den Body nicht direkt über den Handle zu manipulieren, wird dieser private erklärt
    private:
    Body* body;

    Pimple Idiom

    Beim Pointer to implementations Idiom, das auf Herb Sutter zurückgeht, wird insbesonder die Reduzierung der Compilezeit in C++ betont.
    class X {
    public:
    /*... public members... */
    protected:

    /*... protected members?... */
    private:
    /*... private members?... */
    struct Ximpl;
    XImpl* pimpl_; // opaque pointer to

    // forward-declared class
    • Durch das Auslagern der privaten, nichtvirtuellen Funktionen und privaten Variablen in den Body Ximpl, ist es möglich, Veränderungen an Ximpl durchzuführen, ohne das der Handle X neu kompiliert werden muß.
    • Gerne wird der Body durch eine Smart Pointer ersetzt, um abzusicher, daß jeder Handle an genau einen Body gebunden ist
    class X{

    ...
    struct Ximpl;
    boost::scoped_ptr< Ximpl > pimpl;

    ...
    };
    • oder das mehrere Handle einen Body referenzieren können.
    class X{

    ...
    struct Ximpl;
    boost::shared_ptr< Ximpl > pimpl;

    ...
    };
    • Die obigen Bodys werden genau dann gelöscht, wenn der letzte Handle gelöscht wird.
  • C++

    KurzUndGut3

    Beschreibung:

    C++ ist eine komplexe Sprache mit vielen subtilen Facetten. Insbesondere Programmierer, die von einer anderen Programmiersprache umsteigen oder nur gelegentlich in C++ programmieren, haben ihre Schwierigkeiten mit ähnlichen und doch nicht identischen Features in Java oder C.

  • C++0x

    Surprisingly, C++0x feels like a new language: The pieces just fit together better 
    than they used to and I find a higher-level style of programming more natural than before
    and as efficient as ever. (Bjarne Stroustrup)
    right C++0x bricht nicht mit C++ Code.

    Historie

    • Zeitachse C++:
      C__Timeline.gif
    • ARM C++
      • Annotated C++ Reference Manual (ARM)
      • erster C++ Standard
      • Grundlage für C++98
      • Bjarne Stroustrup definiert C++ Funktionsumfang
    • C++98 (ISO/IEC 14882)
      • aktuelle ISO C++ Standard
    • C++03
      • technische Korrektur des Standards
    • TR1
      • Technical Report 1
      • Ergänzungen der C++ Standard Library

    Ziele von C++0x

    Den

    Prinzipien von C++

    • C++ ist eine Multi-Paradigmen Programmiersprache.
    • Vertraut dem Programmierer,
    • Zahle nicht für das, was du nicht benutzt.
    • Brich keinen funktionierenden Code.
    • Kontrolle zur Kompilezeit ist besser als zu Laufzeit.
    bleibt C++0x treu und legt insbesondere Wert auf

    Prinzipien von C++0x

    • Bessere Systemprogrammierung ermöglichen.
    • Bessere Sprache für die Implementierung von Bibliotheken.
    • Soll leichter zu lehren und zu lernen sein.

    Kernsprache

    Mächtigere Klassendefinition

    Ein paar neue Konstrukte:
    class NonCopyableClass{
    NonCopyableClass & operator=(const NonCopyableClass&) = delete;

    NonCopyableClass(const NonCopyableClass&) = delete;
    NonCopyableClass() = default;

    };

    class BaseClass{
    virtual void foo();

    void bar();
    };

    class DerivedClass [[base_check]]: public BaseClass{

    void foo [[override]] ();
    void foo [[override]](int)

    void bar [[hiding]] ();
    void bar [[hiding]](int);

    };

    class DelegateConstructor {
    int value;
    public:

    DelegateConstructor(int v) : value(v) {}

    DelegateConstructor() : DelegateConstructor(17) { }
    };


    class BaseClass{
    public:
    BaseClass(int i);

    };

    class DerivedClass : public BaseClass{
    public:

    using BaseClass::BaseClass;
    };

    template<typename T> class MyVector {

    public:
    MyVector(std::initializer_list<T> list); // initializer-list constructor
    MyVector(const MyVector&); // copy constructor

    MyVector(MyVector&&); // move constructor
    MyVector& operator=(const MyVector&); // copy assignment

    MyVector& operator=(MyVector&&); // move assignment
    };

    defaulted und deleted Standardmethoden

    • die Klasse NonCopyableClass ist nicht kopierbar
    • delete: unterdrückt sowohl den automatisch erzeugten Kopier- als auch den Zuweisungsoperator
    • default: ein Defaultkonstruktor wird erzeugt, für dessen Implementierung der Kompiler sorgt
      • implementiert der Anwender in einer Klasse ein Konstruktor, wird in C++ der Defaultkonstruktor nicht erzeugt right der Anwendert muß diesen selber schreiben
    • void *operator new(std::size_t) = delete; führt in einer Klasse dazu, das diese nicht mehr dynamisch mittels new erzeugt werden kann

    override und hiding von Methoden

    • durch das Attribut base_check der Klasse DerivedClass muß das Überschreiben oder Verstecken von Basisklassenmethoden explizit ausgedrückt werden
    • override: diese Methode will eine Methode überschreiben
    • hidding: diese Methode will eine Methode verstecken (void bar() versteckt die gleichnamige Methode der Basisklasse)
    • sowohl void foo [[override]](int) als auch void bar [[hidding]](int) sind Syntaxfehler

    Delegation von Konstruktoren

    • gemeinsame Funktionalität aller Konstruktoren wurde in C++ in einer init() Methode angeboten, die alle Konstruktoren aufzurufen hatten
    • die Konstruktoren der Klasse DelegateConstructor rufen explizit oder implizit den Konstruktor DelegateConstructor(int v) auf, der darfür sorgt, das value auf 17 initialisiert wird

    Vererbung von Konstruktoren

    • using BaseClass::BaseClass: alle Konstruktoren der Basisklasse in der abgeleiteten Klasse stehen zur Verfügung

    Initialiser List Konstruktor

    • MyVector(std::initializer_list list) ist ein neuer Konstruktor, der über eine Sequenz von homogenen Wert initialisiert werden kann: MyVector myIntVec={1,2,3,4,5}
    • jeder Standardcontainer (vector, list, deque, map, multimap, set, multiset) besitzen diesen Konstruktor, so dass sich diese direkt mit Aggregaten befüllen lassen
    • eigene Datentypen oder Funktionen können auch direkt mittels der Sequenz (std::initializer_list list) mit Aggregaten umgehen:
      double sumAll(std::initializer_list list)

    Move Semantic

    • Ziel der Move Semantic ist es unnötiges Kopieren zu vermeiden
    • MyVector(MyVector&&); und MyVector& operator=(MyVector&&) werden move constructor und move assignment genannt
    • syntaktisch unterscheiden sich diese vom copy constructor und copy assignment durch zwei && (rvalue Referenzen)
    • sie statten die Daten mit einer Move Semantic aus, den beim Erzeugen von neuen aus alten Objekten werden die Inhalte transferiert und nicht kopiert
    • Beispiel: Copy Semantic versus Move Semantic
    std::cout << "move semantic for vector: " << std::endl;
    std::vector <int> vec1={1,2,3,4,5,6,7,8,9};

    std::vector <int> vec2;
    std::cout << "vec1.size(): " << vec1.size() << " vec2.size(): " << vec2.size() << std::endl;

    vec2= std::move(vec1);
    std::cout << "vec1.size(): " << vec1.size() << " vec2.size(): " << vec2.size() << "\n\n";

    std::cout <<"copy semantiv for vector: " << std::endl;
    std::vector <int> vec3;

    std::cout << "vec2.size(): " << vec2.size() << " vec3.size(): " << vec3.size() << std::endl;

    vec3= vec2;
    std::cout << "vec2.size(): " << vec2.size() << " vec3.size(): " << vec3.size() << std::endl;
    • der Codeschnipsel erzeugt folgende Ausgabe
      move semantic for vector:
      vec1.size(): 9 vec2.size(): 0
      vec1.size(): 0 vec2.size(): 9

      copy semantiv for vector:
      vec2.size(): 9 vec3.size(): 0
      vec2.size(): 9 vec3.size(): 9
    • das ganze visualisiert
      • Move Semantik:
        MoveSemantic.gif
      • Copy Semantik:
        CopySemantic.gif

    Die Move Semantic stellt bei Container ein nicht zu unterschätzendes Optimierungspotential dar. Sowohl die Initializer List als auch die Move Semantic Konstruktoren bietet jeder Standardcontainer in C++0x an.

    Einfacheres Arbeiten mit der Standard Library

    Der hochaktuelle gcc 4.5 kann bis auf die range basierte for loop for (auto itr : myInt) { std::cout <<  *itr << " ";} das folgende Codeschnipsel übersetzten

    std::vector <int> myInt(10);
    std::cout << "vector of 10 times 0: ";

    for (auto itr = myInt.begin(); itr != myInt.end(); ++itr){

    std::cout << *itr << " ";
    }
    std::cout << "\n\n";

    // for (auto itr : myInt) { std::cout << *itr << " ";}
    // for (std::vector<int>::const_iterator itr= intInt.begin(); itr != myInt.end(); ++itr){... }

    std::iota(myInt.begin(), myInt.end(),1);

    std::cout << "Increment each value by 1: ";
    for (auto itr = myInt.begin(); itr != myInt.end(); ++itr){

    std::cout << *itr << " ";
    }
    std::cout << "\n\n";

    std::sort(myInt.begin(),myInt.end(),[](int a, int b){ return a >= b;} );

    std::cout << "Sort the vector of natural numbers: ";
    for (auto itr = myInt.begin(); itr != myInt.end(); ++itr){

    std::cout << *itr << " ";
    }
    std::cout << "\n\n";

    std::cout<< "std::min({1,2,4,0,1,-5,2}): " << std::min({1,2,4,0,1,-5,2}) << "\n\n";

    std::map<std::string,std::string> phonebook =
    { {"Bjarne","+1 (012) 555-1212"},

    {"Herb", "+1 (345) 555-3434"},
    {"Alexandrei","+1 (678) 555-5656"} };

    std::cout << "Get each telephon number: \n";
    for (auto itr = phonebook.begin(); itr!= phonebook.end(); ++itr){

    std::cout << itr->first << ": " << itr->second << "\n;

    }
    • und die Ausgabe
      vector of 10 times 0: 0 0 0 0 0 0 0 0 0 0

      Increment each value by 1: 1 2 3 4 5 6 7 8 9 10

      Sort the vector of natural numbers: 10 9 8 7 6 5 4 3 2 1

      std::min({1,2,4,0,1,-5,2}): -5

      Get each telephon number:
      Alexandrei: +1 (678) 555-5656
      Bjarne: +1 (012) 555-1212
      Herb: +1 (345) 555-3434

    Initialiser Listen

    • {}-Initialiser bieten einheitliche Initialisierung von Objekten an und können für alle Initialisierungen verwendet werden
    • Beispiele:
      • Funktionsaufruf: std::min({1,2,4,0,1,-5,2})
      • std::map: std::map<std::string,std::string> phonebook = { {"Bjarne","+1 (012) 555-1212"} }
      • std::vector: std::vector vec1={1,2,3,4,5,6,7,8,9}
    Base x = Base{1,2};  
    Base* p = new Base{1,2};


    struct Derived : Base {
    Derived(int x, int y) :Base{x,y} {};

    };

    func({a}); // function invocation
    return {a}; // function return value

    Neue Algorithmen

    • std::iota(myInt.begin(), myInt.end(),1) inkrementiert die Elemente des Vektors sukzessive um 1
    • std::min({1,2,4,0,1,-5,2},[](int a,int b){ return abs(a) >= abs(b)})kann eine Vergleichsfunktion übergeben werden
      • neben Funktionspointern und Funktionsobjekten (Funktor) kann die Vergleichsfunktion direkt definiert werden (Lambda Funktion)
    • die restlichen neuen Algorithmen: http://www2.research.att.com/~bs/C++0xFAQ.html#algorithms

    Lambda Funktionen

    • Aufbau: [Bindung](Argumente){Funktionskörper}
      • []: Bindung an den lokalen Scope (Lambda Funktionen sind Closures)
        • []: keine Bindung
        • [=]: Werte werden kopiert
        • [&]: Werte werden referenziert
      • (): Argumente (optional) der Lambda Funktion
      • {}: Funktionskörper,
        • ein einzelner Ausdruck ist gleichzeitig der Returnwert
        • kann auch Anweisungen enthalten right return Anweisung notwendig
    • die Details können unter http://www.informit.com/guides/content.aspx?g=cplusplus&seqNum=25 nachgelesen werden
    • Beispiele:
    std::map< const char , std::tr1::function<double(double,double)> > dispTable;

    dispTable.insert( std::make_pair('+',[](double a, double b){ return a + b;}));

    dispTable.insert( std::make_pair('-',[](double a, double b){ return a - b;}));

    dispTable.insert( std::make_pair('*',[](double a, double b){ return a * b;}));

    dispTable.insert( std::make_pair('/',[](double a, double b){ return a / b;}));

    std::cout << "3+4= " << dispTable['+'](3,4) << std::endl;

    std::cout << "3-4= " << dispTable['-'](3,4) << std::endl;

    std::cout << "3*4= " << dispTable['*'](3,4) << std::endl;

    std::cout << "3/4= " << dispTable['/'](3,4) << std::endl;

    []{
    std::cout << "inline lambda";
    std::cout << " function.\n";

    }();

    auto myAdd= [](int a, int b){return a + b;};

    std::cout << "myAdd(1,2): " << myAdd(1,2) << std::endl;
    • dispTable ist ein dispatch Table mit Hilfe von Lambda Funktionen und den neuen Wrapper für Funktionsobjekte std::tr1
    • die namenslose Funktion wird in-place augerufen
    • myAdd bindet die lambda Funktion, dessen Typ durch auto bestimmt wird
    • Ausgabe:
      3+4= 7
      3-4= -1
      3*4= 12
      3/4= 0.75
      inline lambda function.
      myAdd(1,2): 3

    Type Inference

    auto
    • auto erlaubt es, den Typ aus einem Initialiser abzuleiten
    • damit können Schleifen deutlich kompaker geschrieben werden
    std::vector <int> myInt{1,2,3,4,5};

    for (auto itr = myInt.begin(); itr != myInt.end(); ++itr){

    std::cout << *itr << " ";
    }
    for (auto itr : myInt) {
    std::cout << *itr << " ";

    }
    decltype
    • decltype erlaubt es, den Typ aus einem Ausdruck abzuleiten
    • damit lässt sich der Typ von komplexen Berechnungen ohne Template Metaprogramming Magic bestimmen
    std::vector <int > myVecInt={1};
    std::vector <double> myDoubleInt={1.1};

    decltype(myVecInt[0]*myDoubleInt[0]) myDouble = 2.1;

    void foo(const vector<int>& a, vector<float>& b){

    typedef decltype(a[0]*b[0]) ErgTyp;

    for (int i=0; i<b.size(); ++i) {

    ErgTyp* p = new ErgTyp(a[i]*b[i]);

    ...
    • myDouble besitzt letztendlich den Typ, den es verspricht
    • typedef decltype(a[0]*b[0]) ErgTyp erklärt einen neuen Typ, der sich aus der Multiplikation ergibt right float

    Generische Programmierung

    Variadic Templates

    • Templates, die eine beliebige Anzahl von Elementen annehmen können
    • das folgende Programm stellt zwei Variadic Templates vor
      • die Templatefunktion f kann mit einer beliebigen Zahl von Argumenten umgehen
      • die Templatefunktion printCommaSeparatedList, die in zwei Ausprägungen vorliegt, gibt eine beliebige Anzahl von Elementen aus
    #include <iostream>
    #include <string>
    #include <vector>

    template <typename... Args>
    int f(Args... args){

    return (sizeof... args);
    }

    template<typename T>

    void printCommaSeparatedList(T value)
    {
    std::cout<<value<<std::endl;

    }

    template<typename First,typename ... Rest>
    void printCommaSeparatedList(First first,Rest ... rest)

    {
    std::cout<<first<<",";
    printCommaSeparatedList(rest...);
    }


    int main() {
    std::cout << "\n";

    auto intList= {1,2,3};
    std::vector <int> intVec= {1,2,3,4,5};

    std::cout << "f() has " << f() << " arguments\n";

    std::cout << "f(42, 3.14) has " << f(42, 3.14) << " arguments\n";

    std::cout << "f(\"one\",\"two\",\"three\",\"four\") has " << f("one","two","three","four" )
    << " arguments\n";

    std::cout << "f(intVec,intList) has " << f(intVec,intList) << " arguments\n\n";

    printCommaSeparatedList("Only primary template used.");
    printCommaSeparatedList(42,"hello",2.3,'a');

    std::cout << "\n";
    }
    • erzeugt die Ausgabe:
      f() has 0 arguments
      f(42, 3.14) has 2 arguments
      f("one","two","three","four") has 4 arguments
      f(intVec,intList) has 2 arguments

      Only primary template used.
      42,hello,2.3,a
    • Wie funktioniert nun das ganze?
      • das entscheidende sind die drei Punkte, die entweder links template <typename... Args> oder rechts int f(Args... args){ vom Parameter Argsstehen
        • links von Parameter packt der Ellipsen-Operator... das sogenannte parameter pack, rechts entpackt er es wieder
      • eine Besonderheit ist der neue Operator sizeof... , der direkt mit parameter packs umgehen kann
      • f nimmt alle Argumente an und reicht sie an den neuen Operator sizeof... durch
      • printCommaSeparatedList, besteht besteht aus dem Template, das ein Argument verlangt und dem, das mehr als ein Argument erwartet
        • beim Aufruf mit einem Argument printCommaSeparatedList("Only primary template used.") wird das primäre Template verwendet und der String ausgegeben
        • der Aufruf printCommaSeparatedList(42,"hello",2.3,'a') stösst das Template mit mehreren Argument an, gibt das erste Argument mit der Anweisung std::cout<<first<<","; aus, und ruft sich rekursiv mit der Anweisung printCommaSeparatedList(rest...); auf
        • diese Rekursion terminiert genau dann, wenn rest nur noch ein Element enthält, denn jetzt wird das primäre Template verwendet
    Pattern Matching und rekursiver Aufrufe sind elementare Bausteine der funktionalen Programmierung, die hier zur Anwendung kommen.

    Ein interessanteres Beispiel für Variadic Templates ist die printfFunktion. Im Gegensatz zu ihrem C Pendant ist sie typsicher.

    Konstante Ausdrücke

    • Template Metaprogramming zeichnet aus, das der Code zur Compilerzeit ausgeführt wird und somit zu Laufzeit als Konstante zur Verfügung steht
    • hierzu ist es notwendig, das die Ausdrücke zur Kompilezeit evaluiert werden können right neues Schlüsselwort constexpr
    • durch constexpr können einfache Funktionen, die einen Returnwert besitzen oder auch Objekte als konstante Ausdrücke deklariert werden
    • Beispiel:
    constexpr int square(int x) { return x * x; }

    int values[square(7)];
    • erst durch constexpr ist der Aufruf square(7) möglich

    Zusicherungen zur Kompilezeit

    • static assert erlaubt es konstante Ausdrücke ohne Template Metaprogramming Magic zur Kompilezeit zu evaluieren
    • damit können Voraussetzungen an die Templateparameter geprüft werden und gegebenfalls lesbare Fehlermeldungen ausgegeben werden
    • static_assert(sizeof(int) ==  4, "This code only works for sizeof(int) == 4") stellt sicher, das int die Länge 4 besitzt

    Weitere Features

    Standard Library

    Thread

    • C++0x besitzt eine einheitliche Threading Schnittstelle
    • die Thread Schnittstelle ist eines der letzten Featues in C++0x

    Memory Modell

    Grundproblem
    Schreibt ein Thread eine gemeinsame Variable während ein andere diese liest, ist das Verhalten nicht deterministisch.
    x=y=0

    Thread 1 Thread 2
    x=1 y=1
    r1=y r2=x

    Welchen Wert hat r1 und r2 am Ende des Programms? Können beide Werte 0 sein? right sowohl Optimierung auf Hardware Ebene (Schreibepuffer) wie auch Standard Compiler Transformationen machen dies möglich (Bruch der sequentiellen Consistenz)
    Lösung:
    1. Locks
      Thread 1                 Thread 2
      lock(l) lock(l)
      x= 1; y=1
      r1=y r2=x
      unlock(l) unlock(l)
    2. Atomics
      atomic x=y=0

      Thread 1 Thread 2
      x=1 y=1
      r1=y r2=x
    • durch atomic von x und y werden die Schreibaktionen atomar und werden sofort zwischen den Threads sichtbar
    • atomare Datentypen entsprechen im Standardfall von C++0x den volatilen Datentypen in Java
    • die C++ Datentypen volatile haben nichts mit Threading zu tun
    • das C++0x ist an das Javas Memory Modell angelehnt
    • atomare Datentypen erlauben lockfreies Programmieren
    Ein Memory Modell besteht aus:
    1. atomaren Operationen right ohne sie ist Synchronisation nahezu unmöglich
    2. partielle Ordnung von Operationen right Reihenfolge von Operationen, die der Compiler nicht verändern darf (_happens before_)
    3. Speichersichbarkeit right Zeitpunkt, ab dem der Speicher für alle Threads den gleichen Wert besitzt
    4. Data Race Semantik right Grundlage für Optimierungen des Compilers, sodass der Code seine Bedeutung behält
    persons Das Double Checked Locking Pattern ist eine optimierte Spezialform des SingletonPattern in Multithreaded Umgebungen. Es ist aber ohne eine Memory Modell nicht Threadsicher. right http://www.aristeia.com/Papers/DDJ_Jul_Aug_2004_revised.pdf

    Lebenszeit

    • std::thread(fun) startet einen Thread, wobei func eine Funktionspointer oder ein Funktor ist
    • func kann belieg viele Argumente annehmen right CppNext#Variadic_Templates
    class Work{
    public:

    void operator()(int i,std::string s,std::vector<double> v);

    };
    std::thread t(Work(),42,"hello",std::vector<double>(23,3.141));

     

    • join lässt den Vaterthead auf die Beendigung des Threads warten, detach löst die Lebenszeit des Threads vom Vater

    Schutz von Daten

    • es gibt verschiedene Muteces
      1. nicht rekursiv (std::mutex)
      2. rekursiv (std::recursive_mutex)
      3. nicht-rekursiv mit Zeitvorgaben (std::timed_mutex)
      4. rekursiv mit Zeitvorgaben (std::recursive_timed_mutex)
    • Muteces sollten nicht direkt verwendet, sondern in std::unique_lock<> oder std::lock_guard<> gekapselt werden
    • Beispiel: einfacher Lock
    std::mutex m;
    my_class data;

    void foo(){

    std::lock_guard<std::mutex> lk(m);
    process(data);

    }
    • versuche das Lock 3 Millisekunden lang zu bekommen
    std::timed_mutex m;

    my_class data;

    void foo(){
    std::unique_lock<std::timed_mutex> lk(m,std::chrono::milliseconds(3)); // wait up to 3ms

    if(lk) process(data);
    }
    • verzöge das Locken der Ressoure und Locke alle Ressourcen gleichzeitig
    struct X{
    std::mutex m;

    int a;
    std::string b;
    };

    void foo(X& a,X& b){

    std::unique_lock<std::mutex> lock_a(a.m,std::defer_lock);

    std::unique_lock<std::mutex> lock_b(b.m,std::defer_lock);

    std::lock(lock_a,lock_b);

    // process a and b
    }
    • Vorteile
      • kapseln des Locks in std::lock_guard bzw. std::unique_lock
        right Lock wird automatisch wieder freigegeben, sobald die Lock-Wächter out of scope gehen; Anwendung des RAII Idioms
      • std::lock verhindert Deadlocks, die durch Aufrufe der Form foo(x,y) und foo(y,x) resultieren können
        right beiden Muteces werden atomar gelockt

    Initialisierung der Daten

    Das threadsicher Initialisieren von Daten ist auf mehrer Arten möglich.
    class MyClass{
    int i;
    public:
    constexpr MyClass():i(0){}

    MyClass(int i_):i(i_){}
    };

    MyClass x; // 1
    void bar(){
    static myClass z(42); // 2

    }
    MyClass* p=0;
    std::once_flag p_flag; // 3

    void create_instance(){
    p=new MyClass(43);

    }

    void baz(){
    std::call_once(p_flag,create_instance); // 3

    }
    • constexpr stellt sicher, das (1) zur Compilezeit initialisiert wird (CppNext#Konstante_Ausdrücke)
    • statische Variablen in einem Block Scope (2) werden threadsafe initialisiert
    • durch std::once_flag in Kombination mit std::call_once wird die Funktion (3) genau nur einmal ausgeführt

    Signale zwischen Threads

    • durch Bedingungsvariablen (condition variables) können Thread sich über Events synchronisieren
    std::mutex m;
    std::condition_variable cond;

    bool data_ready;

    void process_data();

    void foo(){

    std::unique_lock<std::mutex> lk(m);
    cond.wait(lk,[]{return data_ready;}); // (1)

    process_data();
    }

    void set_data_ready(){
    std::lock_guard<std::mutex> lk(m);

    data_ready=true;
    cond.notify_one(); // (2)
    }
    • cond.wait(lk,[]{return data_ready;}) hebt den Lock auf, schaut nach ob data_ready gilt und fährt gegebenfalls mit dem prozessieren der Daten fort
    • cond.notiy_one() signalisiert, das die Daten fertig sind

    Thread lokale Daten

    • durch thread_localwerden Daten definiert, die
      • dem Thread gehören
      • ihre Lebenszeit mit dem Thread teilen
      • statische Variablen in dem Sinne sind, das sie beim ersten Mal initialisiert werden
    std::string foo(std::string const& s2){
    std::thread_local std::string s="hello";

    s+=s2;
    return s;
    }
    • der String s beim ersten Durchlauf initialisiert und s2 erweitert
    • jeder weitere Durchlauf erweitert s um den Eingabeparameter s2

    Futures

    Starte eine Task(Job in einem neuen Thread), warte aber nicht auf ihren Wert, sondern hole ihn später ab
    • Future: der Thread, der den Wert abholt
    • Promise: der Thread, der den Wert liefert
    std::async
    template<class T, class V> 

    struct Accum { // simple accumulator function object
    T* b;
    T* e;

    V val;
    Accum(T* bb, T* ee, const V& v) : b{bb}, e{ee}, val{vv} {}

    V operator() () { return std::accumulate(b,e,val); }

    };

    double compute(std::vector<double>& v){

    //spawn many tasks if v is large enough
    if (v.size()<10000) return std::accumulate(v.begin(),v.end(),0.0); (1)

    auto f0 {std::async(Accum{&v[0],&v[v.size()/4],0.0})};

    auto f1 {std::async(Accum{&v[v.size()/4],&v[v.size()/2],0.0})};

    auto f2 {std::async(Accum{&v[v.size()/2],&v[v.size()*3/4],0.0})};

    auto f3 {std::async(Accum{&v[v.size()*3/4],&v[v.size()],0.0})};

    return f0.get()+f1.get()+f2.get()+f3.get();

    }
    • abhängig von der Grösse des Vektors v werden vier neue Threads gestartet und in diesen die entsprechenden Werte akkumuliert
    • die ganze Berechnung wird durch f0.get()+f1.get()+f2.get()+f3.get() synchronisiert (geblockt), den nun werden die Ergebnisse eingesammelt
    • zum jetztigen Zeitpunkt ist noch nicht klar, ob std::asyncin den aktuellen C++0x Standard aufgenommen wird
      • std::package_task verpackt eine Funktion(Funktionsobjekt) auf ähnliche Weise in einer Task
    std::promise
    • setze den Rückgabewert der Task set_value oder set_exception
    void asyncFun(std::promise<int> intPromise){

    int result;
    try {
    // calculate the result
    intPromise.set_value(result);

    } catch (MyException e) {
    intPromise.set_exception(std::copy_exception(e));

    }
    }
    std::future
    • starte die Task (2), verbinde dich mit dem Promise durch get_future (1) und hole den Wert mit get (3) ab
    std::promise<int> intPromise;
    std::unique_future<int> intFuture = intPromise.get_future(); (1)

    std::thread t(asyncFun, std::move(intPromise)); (2)

    // do some other stuff
    int result = intFuture.get(); // may throw MyException (3)
    • unique_future besitzt im Gegensatz zu shared_future Move Semantik
    Unter http://www.stdthread.co.uk/doc/headers.htmlist die Threading API schön beschrieben.

    Smart Pointer.

    Smart Pointer sind spezielle, intelligente Zeiger, die eine gewrappte Ressource mit zusätzlicher Funktionalität ausstatten.
    Die drei neuen Smart Pointer shared_ptr, weak_ptr und unique_ptr lösen die bekannten auto_ptr aus C++ ab. Sie überwachen den Lebenszyklus der Ressource nach dem RAII Idiom. Jeder dieser Smart Pointer besitzt sein spezielles Einsatzgebiet. Sowohl shared_ptr als auch insbesondere unique_ptr besitzen ein ähnliches Interface wie der auto_ptr. Der auto_ptr, der genau eine Ressource kapselt, wird als deprecated erklärt, da er zwei grosse Schwächen besitzt:
    1. beim Kopieren eines auto_ptr wird deren Inhalt mitkopiert right Transfer of Ownership oder Move Semantik
    2. er ist weder kopier- oder zuweisbar right kann nicht in Standardcontainern verwendet werden
    std::auto_ptr<int> auto1(new int(5));

    std::auto_ptr<int> auto2(auto1);
    int a= *auto1;
    • auto_ptr:
      auto_ptr.gif
    • der Aufruf int a= *auto1 ist undefiniert

    Beispiel

    Das Programm
    std::vector< std::tr1::shared_ptr<std::string> > sharedVec;

    std::tr1::shared_ptr<std::string> sharedPtr( new std::string("initial"));

    sharedVec.push_back(std::tr1::shared_ptr<std::string>( sharedPtr ));

    std::cout << "Values: sharedPtr sharedVec" << std::endl;
    std::cout << "Initial: " << " " << *sharedPtr << " " << *sharedVec[0]
    << std::endl;

    *sharedPtr="modfied";
    std::cout << "Modified: " << " " << *sharedPtr << " " << *sharedVec[0]
    << std::endl;

    std::cout << "use_count: " << sharedPtr.use_count() << std::endl;

    {
    std::tr1::shared_ptr<std::string> localSharedPtr( sharedPtr );

    std::cout << "use_count: " << sharedPtr.use_count() << std::endl;

    }
    std::cout << "use_count: " << sharedPtr.use_count() << std::endl;

    std::tr1::weak_ptr<std::string> weakPtr( sharedPtr );
    std::cout << "use_count: " << sharedPtr.use_count() << std::endl;

    std::unique_ptr<std::string> uniquePtrFirst( new std::string("only one") );

    // std::unique_ptr<std::string> uniquePtrSecond( uniquePtrFirst); will not compile
    std::unique_ptr<std::string> uniquePtrSecond( std::move(uniquePtrFirst));

    std::cout << "uniquePtrFirst.get(): " << uniquePtrFirst.get() << " *uniquePtrSecond.get(): "
    << *uniquePtrSecond.get() << std::endl;
    liefert die folgende Ausgabe:
    Values:          sharedPtr     sharedVec
    Initial: initial initial
    Modified: modfied modfied
    use_count: 2
    use_count: 3
    use_count: 2
    use_count: 2
    uniquePtrFirst.get(): 0 *uniquePtrSecond.get(): only one

    shared_ptr

    • shared_ptr:
      shared_ptr.gif
    Mehrere shared_ptr können auf eine Ressource verweisen.
    • Referenzzähler
      • inkrementiert, falls eine neuer shared_ptr auf die Ressource erzeugt wird
      • dekrementiert, falls ein shared_ptr gelöscht wird (out of scope)
      • die Ressource wird genau dann gelöscht, wenn der Referenzähler auf 0
        deterministisches Löschen der Ressource != Garbage Collection, den hier wird die Ressource nur freigegeben
    • Destruktor:
      • dem Konstruktor kann eine Funktion oder Funktor mitgegeben werden um die Ressource freizugeben
      • für den shared_ptr
        shared_ptr(Y * p, D d);
        wird im Destruktor
        d(p)
        aufgerufen
    • ist kopier- und zuweisbar right kann in Standard Containern verwendet werden
    • Schwäche:
      • zyklische Abhängigkeiten erlauben es nicht die Ressource freizugeben

    weak_ptr

    Bricht zyklische Refenzen auf.
    • bietet nur ein sehr einfaches Interface an
    • modifiziert nicht den Referenzzähler
    • kann selber keine Ressource besitzen
    • ist nicht wirklich ein Smart Pointer
    • erlaubt im Gegensatz zum shared_ptr und weak_ptr kein transparenten Zugriff auf die Ressource
    • um auf die Ressource eines weak_ptr zuzugreifen, wird diese gelockt und ein shared_ptr damit initialisiert
    std::weak_ptr<X> resource;

    if(shared_ptr<X> px = resource.lock()){
    // use *px to access the resource

    }
    else{
    // resource has expired
    }

    unique_ptr

    Besitz exclusive eine Ressource.
    • unique_ptr:
      unique_ptr.gif
    • unique_ptr ist nahezu Interfacecompatile mit dem auto_ptr Smart Pointern, unterbindet aber dessen fehlerträchtiges kopieren
    • entsprechend zum auto_ptr besitzt der unique_ptr exclusive seine Ressource, die beim Kopieren transferiert wird right Move Semantik
    • unique_ptr lassen sich aber nicht direkt kopieren, sondern müssen die Funktion std::move verwenden
    auto_ptr<int> ap1(new int);

    auto_ptr<int> ap2 = ap1; // OK, albeit unsafe
    unique_ptr<int> up1(new int);

    unique_ptr<int> up2 = up1; // compilation error: private copy constructor inaccessible
    • kann in Algorithmen verwendet werden, in den nur mit Rvalue Referenzen (temporäre Objekte; Objekte ohne Namen) oder expliziten Movekonstruktoren gearbeitet wird
    unique_ptr<int> up(new int)
    unique_ptr<int> up1= std::move(up);
    • erreicht wird dies Verhalten durch einen privaten Kopierkonstruktor und einen öffentlichen Movekonstruktor
    template <class T>
    class unique_ptr{

    public:
    unique_ptr(unique_ptr&& u); // rvalues bind here
    private:
    unique_ptr(const unique_ptr&); // lvalues bind here

    };

    Container

    Tuple

    Der neue Container Typ std::tuple ist eine Verallgemeinerung des bekannten C++ Datentyps std::pair, der Paare verschiedenes Typs binden kann.
    Tuple haben die folgenden Eigenschaften:
    • besitzen eine feste Dimension
    • kann mindestens 10 verschiedene Elemente binden
    • kann in einem Standard Container verwendet werden
    • Tupleinstanzen können genau dann miteinander verglichen werden, wenn sie einerseits die gleiche Länge besitzen und andererseits deren Elemente miteinander vergleichbar sind
    std::tr1::tuple<std::string,int,float> tup1("first tuple",3,4.17);                        (1)

    std::tr1::tuple<std::string,int,double> tup2= std::tr1::make_tuple("second tuple",4,1.1);(1)

    std::cout << std::boolalpha;
    std::cout << std::tr1::get<0>(tup1) << " and " << std::tr1::get<0>(tup2) << std::endl; (2)

    std::cout << "tup1 < tup2: " << (tup1 < tup2) << std::endl; (4)

    std::tr1::get<0>(tup2)= "Second Tuple"; (3)

    std::cout << std::tr1::get<0>(tup1) << " and " << std::tr1::get<0>(tup2) << std::endl; (2)

    std::cout << "tup1 < tup2: " << (tup1 < tup2) << std::endl; (4)
    • Tuple können sowohl durch einen Kunstruktor als auch durch die Hilfsfunktion std::tr1::make_tuple erzeugt werden (1)
    • auf einzelne Elemente kann mittels std::tr1::get<ind>(tup) lesend (2) und gegebenfalls schreibend (3) zugegriffen werden zugegriffen
    • Tuple unterstützen Vergleiche (4)
    Die Ausgabe:
    first tuple and second tuple
    tup1 < tup2: true
    first tuple and Second Tuple
    tup1 < tup2: false

    Array

    array ist ein Standard Template Library (STL) konformer Container Wrapper for Arrays fester Länge.

    Containerstatische GrösseSTL konformkontinuierlicher Speicherbereich
    C-Array DONE   DONE
    std::vector   DONE DONE
    std::array DONE DONE DONE


    Das C++0x std::array verbindet das Laufzeitverhalten des C-Arrays mit dem Interface des C++ std:vector.

    std::tr1::array <int,8> a1={1,2,3,4,5,6,7,8};                (1)

    std::tr1::array <int,8> a2={1,2,3,4,5}; (1)

    std::copy( a1.begin(), a1.end(), (2)
    std::ostream_iterator< int >(std::cout," "));

    std::cout << "\n";
    std::copy( a2.begin(), a2.end(), (2)

    std::ostream_iterator< int >(std::cout," "));
    std::cout << "\n";

    a2[7]=8; (3)
    std::copy( a2.begin(), a2.end(), (2)

    std::ostream_iterator< int >(std::cout," "));
    std::cout << "\n";

    std::cout << "std::accumulate(a2.begin(),a2.end(),0): " (2)
    << std::accumulate(a2.begin(),a2.end(),0) << "\n";

    std::cout << "a2.size(): " << a2.size() << std::endl; (2)
    • der Konstruktor von std::array verlangt ein Aggregat; fehlende Werte werden Defaultinitialisiert (1)
    • std::array kann in den Algorithmen der STL verwendet werden (2)
    • als Array erlaubt es natürlich den Indexzugriff (3)
    Die Ausgabe:
    1 2 3 4 5 6 7 8
    1 2 3 4 5 0 0 0
    1 2 3 4 5 0 0 8
    std::accumulate(a2.begin(),a2.end(),0): 23
    a2.size(): 8

    Ungeordnete Assoziative Container (Hashtabelle)

    Die Ungeordneten Assoziativen Container verhalten sich wie die bekannten Geordneten Assoziativen Container.
    Der feine Unterschied ist
    1. die Schlüssel der ungeordneten Datentypen sind nicht geordnet right konstante Zugriffszeit ist möglich
    2. die geordneten Datentypen haben logarithmische Zugriffszeit
    3. die Elemente der Ungeordneten Assoziativen Container müssen nicht vergleichbar sein
    Die Hashtabelle schafften es nicht mehr in in C++98 Standard right viele Compilerbauer boten ihre eigene Erweiterungen an rightNamen wie hash_map waren schon vergeben
    • es gibt vier verschiedene Ungeordnete Assoziative Arrays
      • unordered_set: eindeutige Schlüssel
      • unordered_multiset: mehrere gleiche Schlüssel möglich
      • unordered_map: nur ein (Schlüssel,Wert) Paar mit gleichem Schlüssel erlaubt
      • unordered_multimap: mehrere (Schlüssel,Wert) Paare mit gleichem Schlüssel möglich
    • jedem Ungeordneten Assoziativen Array steht aus C++98 ein Geordnetes Assoziatives Array gegenüber
      Ungeordnete Assoziative ArraysGeordnete Assoziative Arrays
      std::unordered_set std::set
      std::unordered_multiset std::multiset
      std::unordered_map std::map
      std::unordered_multimap std::map
    • die Anwendung der Ungeordneten Assoziativen Arrays ist ziemlich unspektakulär, da dieser der der Geordneten Assoziativen Arrays entspricht
    std::map<std::string,int> m { {"Dijkstra",1972},{"Scott",1976},{"Wilkes",1967},{"Hamming",1968} };

    m["Ritchie"] = 1983;
    for(auto x : m) std::cout << '{' << x.first << ',' << x.second << '}';

    std::unordered_map<std::string,int> um { {"Dijkstra",1972},{"Scott",1976},{"Wilkes",1967},{"Hamming",1968} };

    um["Ritchie"] = 1983;
    for(auto x : um) std::cout << '{' << x.first << ',' << x.second << '}';

    Reguläre Ausdrücke

    Der Umgang mit regulären Ausdrücken in C++0x lässt sich in drei Schritte zerlegen.

    std::regex rgx(R"\d+");                                           (1)

    std::smatch match; (2)
    if (std::regex_search(std::string("123A43"), match, rgx)) (3)

    std::cout << "match found after " << match.prefix() << '\n';
    1. std::regex rgx(R"\d+")hält den regulären Ausdruck
      • R"\d+" bezeichnet einen Raw String Literal in C++0x
      • der String folgt per Default der ECMAScript Grammatik, aber auch POSIX BRE, ERE, awk, grep, egrep oder sed Syntax ist möglich
      • neben std::regex gibts auch std::wregex für wide characters wchar_t
    2. std::smatch matcherhält das Ergebnis der Suche
      • match[0]: der Gesamtmatch
      • match[i]: i>0 sind die Teilmatches
      • position , suffix , prefix und length liefern weiter Informationen
    3. std::regex_searchverarbeitet das Suchergebniss weiter
      • std::regex_match: verlangt einen genauen Treffer und gibt ein Boolean zurück
        std::regex_match("bd",std::regex(R"b(c*)d"))
      • std::regex_search: schaut nach dem ersten geeignete Sequenz
      • std::regex_replace: schaut nach einem Treffer und ersetzt ihn

    Wiederholtes Suchen

    Mit std::regex_iterator und std::regex_token_iteratorlässt sich ein Iterator über eine Eingabesequenz definieren.
    • std::regex_iterator: liefert die Zeichen, die dem regulären Ausdruck entsprechen
    • std::regex_token_iterator: splittet die Eingabesequenz mit Hilfe der regulären Ausdrücke right Tokenizer
    std::cout << std::endl;
    boost::regex reg1("[^13579,]");

    std::string str1="1,2,3,4,5,6,7,8,9,10,11,12";
    boost::sregex_iterator it1(str1.begin(),str1.end(),reg1);

    boost::sregex_iterator end1;
    std::cout << "Character Stream: " << std::endl;

    while (it1 != end1) std::cout << " " << *it1++;

    std::cout << "\n\n\n";

    boost::regex reg2(",");

    std::string str2="1,2,3,4,5,6,7,8,9,10,11,12";
    boost::sregex_token_iterator it2(str2.begin(),str2.end(),reg2,-1);

    boost::sregex_token_iterator end2;
    std::cout << "Token Stream: " << std::endl;

    while (it2 != end2) std::cout << " " << *it2++;

    std::cout << "\n";
    std::cout << std::endl << std::endl;
    • it1(str1.begin(),str1.end(),reg1) gibt einen Iterator über alle Zahlen aus "1,2,3,4,5,6,7,8,9,10,11,12" zurück, die nicht in reg1("[^13579,]") sind
    • it2(str2.begin(),str2.end(),reg2,-1) gibt einen Iterator über die kommaseparierten reg2(",") Tokens des Strings "1,2,3,4,5,6,7,8,9,10,11,12" zurück
    • Ausgabe:
      Character Stream:
      0 2 4 6 8 0 2

      Token Stream:
      1 2 3 4 5 6 7 8 9 10 11 12

    Zufallszahlen

    Zufallszahlen sind in vielen Bereichen notwendig:
    • Tests
    • Spiele
    • Simulationen
    • Sicherheit
    Der Zufallszahlengenerator (std::variate_generator) besteht aus zwei Teilen. Einem Generator, der Sequenzen von Zufallszahlen erzeugt und einer Verteilung, die die Zufallszahlen in einem vorgegebenem Bereich verteilt.

    std::tr1::mt19937 rng;                     // generator

    std::tr1::uniform_int<> six(1,6); // distribution
    std::tr1::variate_generator<std::tr1::mt19937, std::tr1::uniform_int<> > dice(rng, six);

    for ( int i=1; i<= 9; ++i){

    std::cout << "dice["<< i << "]: " << dice() << std::endl;

    }
    • Ausgabe
      dice[1]: 5
      dice[2]: 1
      dice[3]: 6
      dice[4]: 6
      dice[5]: 1
      dice[6]: 6
      dice[7]: 6
      dice[8]: 2
      dice[9]: 4
    Ein Überblick über verschiedene Generatoren und Verteilungen ist unter http://www.boost.org/doc/libs/1_41_0/libs/random/index.html. Dies entspricht annähernd der Funktionalität von C++0x.

    Type Traits

    Ein Program, das ein anderes Program erzeugt, nennt sich Metaprogramm. Tut sie dies noch zur Compilezeit mit Templates, dann heißt diese Technik Static Template Metaprogramming. Die type_traits Bibliothek erlaubt Typabfragen, Vergleiche und Modifikationen zur Compilezeit. Damit lassen sich Algorithmen speziell auf den Datentyp zuschneiden und doch generisch aufrufen.

    #include <tr1/type_traits>
    #include <iostream>

    template <class Ty>
    void multBy3Impl(Ty val, const std::tr1::true_type&){

    std::cout << "Arithmetic type: ";
    std::cout << 3*val << std::endl;

    }

    template <class Ty>
    void multBy3Impl(Ty val, const std::tr1::false_type&){

    std::cout << "No arithmetic type: ";
    std::cout << val << val << val << std::endl;

    }

    template <class Ty>
    void multBy3(Ty val){

    multBy3Impl(val, std::tr1::is_arithmetic<Ty>());
    }

    int main(){
    multBy3(1);
    multBy3(5.5);

    multBy3(std::string("Only for testing purpose. "));
    }
    • das Programm evaluiert zu Compilezeit, ob das Argument von multBy3 ein arithmetischer Typ std::tr1::is_arithmetic ist oder nicht
    • abhängig von dem Ergegnis wird multBy3Impl(Ty val, const std::tr1::true_type&) bzw. multBy3Impl(Ty val, const std::tr1::false_type&) aufgerufen
    • die entsprechende Implementierung multBy3Impl weiß, wie sie den Datentyp zu multiplizieren hat
    • die Ausgabe zeigt, das der Typdispatch zur Compilezeit richtig ausgeführt wird
      Arithmetic type: 3
      Arithmetic type: 16.5
      No arithmetic type: Only for testing purpose. Only for testing purpose.
      Only for testing purpose.
    Typ Abfragen
    • Primary Type Categories ( is_void, is_floating_point, is_array... )
    • Composite Type Categories ( is_arithmetic, is_object, is_member_pointer... )
    • Type Properties ( is_const, is_pod, is_abstract,... )
    Typ Vergleiche
    • Type Relationships ( is_same, is_convertible, is_base_of... )
    Type Modfikationen
    • Typ Transformations ( remove_const, add_const, remove_reference... )
    • Alignment ( alignment_of, aligned_storage )
    Der ganze Rest an Funktionen kann unter http://msdn.microsoft.com/en-us/library/bb982077.aspxnachgelesen werden.
    Optimiertes Kopieren von Datenstrukturen

    Ein generisches Kopieralgorithmus für Standardcontainer kann so aussehen:

    template<typename I1, typename I2, bool b

    I2 copy_imp(I1 first, I1 last, I2 out, const std::tr1::integral_constant<bool, b>&){

    while(first != last){
    *out = *first;

    ++out;
    ++first;
    }
    return out;

    }
    Jedes Element des Bereiches [first,last[ wird sukzessive an den Bereich [out,...[ kopiert.
    Dies geht mit memcpy deutlich schneller.

    template<typename T>                                                            
    T* copy_imp(const T* first, const T* last, T* out, const std::tr1::true_type&){
    memcpy(out, first, (last-first)*sizeof(T));

    return out+(last-first);
    }
    Die Datenstrukturen müssen aber hinreichend einfach sein um memcpyzu verwenden:
    • die Iteratoren müssen Pointer sein ( const T* first, const T* last, T* out )
    • die Iteratoren müssen auf die gleichen Typen verweisen ( <typename T> enthält nur ein Typ)
    • die Elemente des Containers müssen einen trivialen, vom Compiler automatisch erzeugten, Zuweisungsoperator besitzen ( const std::tr1::true_type& )
    Ein Aufruf der Form
    template<typename I1, typename I2>
    I2 copy(I1 first, I1 last, I2 out){

    typedef typename std::iterator_traits<I1>::value_type value_type;
    return copy_imp(first, last, out, std::tr1::has_trivial_assign<value_type>());

    }
    • stösst die richtige copy_imp Implementierung an
    • durch typedef typename std::iterator_traits<I1>::value_type value_type wird der Typ der Containerelemente bestimmt um ihn anschliessend in std::tr1::has_trivial_assign<value_type> zu nutzen
    • abhängig vom Ergebniss der Evaluierung wird der optimierte oder generische Kopieralgorithmus verwendet
    • das Ergebnis des Aufrufs std::tr1::has_trivial_assign<value_type> ist eine Klasse, so dass der Dispatch auf das vierte Argument der copy_imp Methoden vollzogen wird
    • der Ausdruck typedef integral_constant<bool, true> true_type; definiert eine Spezialisierung true_type von std::tr1::integral_constant<bool, b>

     

    Referenz Wrapper

    Referenzen sind in C++ keine First class objects.
        right sie können nicht kopiert werden
        right sie können nicht in Standardcontainern verwendet werden, da sie hierzu copy-constructible and assignable sein müssen
        right dieser Ausdruck lässt sich in C++ nicht kompilieren: std::vector<int&>
    Die C++0x Bibliothek reference_wrapper erzeugt aus Objekten vom Type T& copy-constructible and assignableObjekte. Damit lassen sich
    • Objekte in Containern verwenden, die sich nicht Kopierkonstruierbar und Zuweisbar sind (Dateien, Locks oder auch Netzwerkverbindungen)
    • Objekte mit Referenzsemantik in Containern verwenden
    reference_wrapper ist ein copy-constructible and assignable Wrapper um das Objekt vom Typ T&.

    std::vector<bool> copyVec;
    std::vector< std::tr1::reference_wrapper<bool> > refVec;

    bool b=false;
    copyVec.push_back(b);
    refVec.push_back(std::tr1::ref(b));

    std::cout << std::boolalpha;
    std::cout << "Values: b copyVec refVec" << std::endl;

    std::cout << "Initial: " << b << " " << copyVec[0] << " " << refVec[0] << std::endl;

    b= true;
    std::cout << "Modified: " << b << " " << copyVec[0] << " " << refVec[0] << std::endl;
    right refVec[0] ist eine Referenz auf b und wird durch b= true modifiziert
    • Ausgabe:
      Values:      b       copyVec    refVec
      Initial: false false false
      Modified: true false true
    Die Hilfsfunktion
    • ref<T> gibt einen T& Wrapper Objekt
    • cref<T> gibt einen const T& Wrapper Objekt
    zurück.
    template <typename T>

    void doubleMe(T t){
    ++t;
    };

    int f=1;
    std::cout << "initial value: " << f << std::endl;

    doubleMe(f);
    std::cout << "doubleMe(f): " << f << std::endl;

    doubleMe(std::tr1::ref(f));
    std::cout << "doubleMe(ref(f)) : " << f << std::endl;
    • Ausgabe:
      initial value:     1
      doubleMe(f): 1
      doubleMe(ref(f)) : 2

    bind und function

    Die beiden Librarys bind und functionergänzen sich sehr gut.
    • bind
      • erlaubt Argumente an beliebige Positionen einer Funktion, Methode oder eines Funktionsobjektes zu binden
      • das resultierende Funktionsobjekt kann direkt aufgerufen, in den Algorithmen der Standard Template Library verwendet oder in einem Funktionsobjekt function gespeichert werden
      • für Argumente ohne Wert können Platzhalter eingeführt werden
    • function
      • ermöglicht die einfache Definition von Funktionsobjekten
      • diese Funktionsobjekten sind first class functions
      • erlauben die einfache Definition von Callbacks
    • bind und function
      • teilweise evaluieren jedes beliebigen Arguments mit bind und binden der neuen Funktion mit function right sehr mächtiges currying lässt sich umsetzen
      • Funktionsobjekte, die mit bind erzeugt werden, können direkt mit function gebunden und später aufgerufen werden.
    Die Funktionalität von bind kann oft durch lambda Funktionen ausgedrückt, die von function wird durch auto angeboten.

    double divMe(double a, double b){

    return double(a/b);
    }


    template <typename T>

    T addMe(T a, T b){
    return a+b;

    };

    int main(){

    std::cout<< "1/2.0= " << bind(divMe,1,2.0)() << std::endl; (1)

    function<double(double)> myDiv= bind(divMe,_1,2.0); (2)

    std::cout<< "1/2.0= " << myDiv(1) << std::endl;

    function<int(int)> addInt = bind(addMe<int>, _1, 2);

    std::cout << "1+2= " << addInt(1) << std::endl;

    function<std::string(std::string)> addString = bind(addMe<std::string>,"first",_1); (3)

    std::cout << "first + second= " << addString("second") << std::endl;

    auto funcObject1= bind(addMe<std::string>, "first",_1);

    std::cout << "first + second= " << funcObject1("second") << std::endl;

    auto funcObject2= bind(addMe<std::string>, _1, _2);

    std::cout << "first + second= " << funcObject2("first","second") << std::endl;

    std::vector<int> myVec{1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20};

    std::copy_if( myVec.begin(), myVec.end(), (4)

    std::ostream_iterator<int>( std::cout, ", " ),
    bind( std::logical_and<bool>(),

    bind( std::greater <int>(),_1,9 ),
    bind( std::less <int>(),_1,16 )));

    std::cout<< std::endl;

    /* compiles only with gcc 4.5 (5)
    std::copy_if( myVec.begin(), myVec.end(),

    std::ostream_iterator<int>( std::cout, ", " ),
    [](int a){ return (a>9)&&(a<16);});
    std::cout << std::endl;
    */

    }
    1. bind(divMe,1,2.0)() definiert einen Funktionskörper, bindet die Argumente 1 und 2.0 und ruft ihn auf
    2. bind(divMe,_1,2.0) bindet das zweite Argument mit 2.0, führt eine Platzhalter _1 für das erste Argument ein, bindet das Funktionsobjekt an function<double(double)> und führt es anschliessend mit myDiv(1) aus
    3. auto funcObject1 erleichert die Definition von Funktionsobjekten
    4. zuviele geschachtelte bind Ausdrücke (ich habe die ganzen Namespaces der Lesbarkeit wegen bekannt gemacht), können verwirrend wirken
    5. diese Funktion bietet die gleiche Funktionalität wie (4) an
    Ausgabe:
    1/2.0= 0.5
    1/2.0= 0.5
    1+2= 3
    first + second= firstsecond
    first + second= firstsecond
    first + second= firstsecond
    10, 11, 12, 13, 14, 15,

    Weitere Bibliotheken

    • time utilities
    • return Wert Evaluierung von Funktionsobjekten mit return_of; die Funktonalität wird schon durch die Alternative Funktionssyntax mit decltype angeboten
    • einfache Aufrufwrapper mit mem_fn; die Funktionalität wird schon durch std::bind angeboten

    Compilerunterstützung

    Weitere Informationen

    Verarbschiedung des Standards

    Mail am 15.03.2010 an comp.lang.c++.moderated von Herb Sutter.
    • March 2010 ISO C++ standards meeting
        ...
      1. Approved Final Committee Draft (FCD) for C++0x

      The biggest news is that this afternoon we voted in the final
      remaining feature changes to C++0x, and to much applause then
      unanimously approved the text for international ballot as a Final
      Committee Draft (FCD). FCD means that, assuming no surprises, we
      intend to do only bug fixes and editorial corrections for the next
      year or so, and then ballot a final standard. If we can do that,
      assuming all goes well, C++0x could officially be published as soon as
      next year as ISO C++ 2011, and we can stop with the "x-is-hex" jokes
      and just start calling it C++11.

      This is a big milestone, and it was achieved thanks to removing a
      couple of controversial features last summer and a whole lot of work
      by the ISO C++ committee members over the past six months in
      particular. That work includes countless hours spent between our full
      face-to-face meetings at face-to-face ad-hoc meetings to swat library
      bugs, teleconferences on resolving core language questions, and
      triple-
      digit person-hours invested in four teleconferences during December-
      February purely about C and C++ compatibility that have greatly helped
      to identify and attempt to resolve minor areas of divergence between
      the C++0x draft standard and the C1x draft standard (as both are now
      in progress; C1x is targeting completion and publication in 2012).

      All in all, your committee members have put in an enormous amount of
      effort to bring this in, and the draft is in far better shape for this
      meeting than anyone could have expected last summer. For comparison,
      in my and several others'; opinions, it's in better shape than the FCD
      of the C++98 standard.
      ...

     

  • C++11 für Programmierer

    C++11 für Programmierer

    Beschreibung:

    Dieser Leitfaden richtet sich an C++-Programmierer, die sich mit dem C++11-Standard vertraut machen möchten. Er zeigt, welche Neuerungen und Erweiterungen der Standard mit sich bringt, wie die neuen Features effizient eingesetzt werden -- und warum C++11 das bessere C++ ist. Das Buch bietet zunächst einen Überblick über die C++11-Features und zeigt dann im Detail, wie Sie die Neuerungen in Ihren Programmen einsetzen können.

  • C++11: Der Leitfaden für Programmierer zum neuen Standard

    Cpp11DerLeitfadenFürProgrammiererZumNeuenStandard

    Beschreibung:

    C++11 ist der neue C++ Standard, der unmittelbar vor der Verabschiedung steht. C++11 macht den C++-Programmierer fit für die Zukunft. Dies umfasst Themen wie Threading, Funktionale Programmierung, automatisches Speichermanagement, reguläre Ausdrücke..., um nur ein paar zu nennen.

     

  •  

    Haskell

    Einstieg

    Haskell ist eine general purpose language, die nach dem Logiker Haskell Curry benannt ist. Haskell erlebt gerade eine Renaissance from resarch to practical language . Initiiert wurde die Renaissance durch Sprachen wie C++, Python, Ruby, und Javascript, die zunehmend funktionale Elemente aufnehmen. Obwohl diese Sprachen nicht rein funktional sind, erlauben sie das Programmieren in einem higher order style.

    Einstiegsliteratur

    Kernidee

    Programme bestehen aus einer Auswertung von Funktionen, die weder einen Zustand noch veränderliche Daten kennen. Funktionen sind Funktionen im mathematischen Sinn. Ihr Ergebnis hängt nur von der Eingabe ab.
    right Der Wert eine Funktion bleibt über den ganzen Programmverlauf gleich, so daß Funktionsaufruf durch den Wert der Funktion ersetzt werden kann (referential transparency).
    Funktionen verhalten sich wie Lookup Table.

    Herausforderung

    • taming effects nach Simon Peyton Jones
      the-challenge-of-effects.jpg

    Charakteristiken funktionaler Programmiersprachen

    Allgemein

    first class functions

    Funktionen lassen sich

    • zur Laufzeit eines Programmes erzeugen
    • in Datenstrukturen speichern
    • als Argument und Rückgabewert einer Funktion verwenden
    • right Funktionen sind Werten sehr ähnlich
    • Beispiel:Dispatch table in Python
      selectLam={
      "+": (lambda x,y: x+y),

      "-": (lambda x,y: x-y),
      "*": (lambda x,y: x*y),

      "/": (lambda x,y: x/y)

      }
    • um die Power Funktion zur Laufzeiterweitert, ergibt:
      >>> selectLam["^"]=input("power: ")
      power: lambda a,b: a**b

      >>> for i in ("+","*","-","/","^"):
      ... print i + ": " , selectLam[i](3,4)
      ...
      +: 7
      *: 12
      -: -1
      /: 0
      ^: 81
      >>>

    Funktionen höherer Ordnung

    higher order functions
    Funktionen höherer Ordnung sind Funktionen, die entweder Funktionen als Argument annehmen oder als Rückgabewert liefern.

    • Beispiele:
      • Dekoratoren in Python
      • mapFunktion in Haskell
        Main> map (\x -> x*x) [1,2,3,4]
        [1,4,9,16]

    reine Funktionen

    pure functions, expressions

    • Gegenüberstellung von pure und impure aus Real World Haskell

      PureImpure
      Always produces the same result when given the same parameters May produce different results for the same parameters
      Never has side effects May have side effects
      Never alters state May alter the global state of the program, system, or world
    • Vorteile:
      • Korrektheitsbeweise durchführen
      • vereinfachtes Refaktoring
      • unbenützte Funktionen oder Teilausdrücke können entfernt werden
      • da die Ausgabe nur von der Eingabe abhängt, können die Ergebnisse zwischengespeichert werden
      • da die Ausführungsreihenfolge der Funktion nicht relevant ist, können die Funktionsaufrufe umgeordnet oder auch parallel ausgeführt werden right Optimierung durch den Compiler
    • Beispiel:Die folgende Zusicherung gilt in Haskell, aber nicht in der imperativen Programmiersprache Python:
      • Haskell:
        *Main> let f10=func(10)
        *Main> f10==func(10)
        True
      • Python:
        >>> f10= f(10)
        >>> assert f10 ==f(10)
        Traceback (most recent call last):
        File "<stdin>", line 1, in <module>
        AssertionError
      • right In Haskell kann jeder Aufruf von f(10) durch den Optimierer ersetzt werden, ohne das Verhalten des Programmes zu ändern.

    Rekursion anstelle von Schleifen und Zuweisungen

    Variablen stehen für unveränderliche Werte und nicht für Speicherstellen, die verändert werden können.

    • imperativ
      >>> def fak(n):
      ... sum=1
      ... for i in range(1,n+1):
      ... sum = sum*i
      ... return sum
      ...
      >>> fak(10)
      3628800
    • funktional
      >>> def fak(n):
      ... if (n==1): return 1
      ... else: return n*fak(n-1)
      ...

      >>> fak(10)
      3628800
    • Funktionen wie fold* in Haskell, reduce in Python oder accumulatein C++ formalisieren die Iteration durch eine Liste
      • Haskell
        Prelude> foldl1 (\x y->x*y) [1..10]
        3628800
      • Python
        >>> reduce(lambda x,y:x*y,range(1,11))
        3628800
      • C++
        for ( unsigned int i=1;i <= 10; ++i) intVec.push_back(i);

        std::cout << "mult: " << std::accumulate(intVec.begin(),intVec.end(),1,_1*_2)<<"\n";

    Verarbeitung von Listen

    LISP steht für LISt Processing.

    • Haskell
      • map nachprogrammiert
        myMap :: (t -> a) -> [t] -> [a]
        myMap f [] = []
        myMap f (x:xs)= f x: myMap f xs
      • ergibt
        *Main> myMap (\x -> x*x) [1..10]
        [1,4,9,16,25,36,49,64,81,100]
    • Python
      • Map mit list comprehension
        >>> [ x*x for x in range(1,11)]
        [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
    • C++
      • mit Boost::lambda
        for ( unsigned int i=1;i <= 10; ++i) intVec.push_back(i);

        std::transform( intVec.begin(), intVec.end(),intVec.begin(),_1*_1);

    Seiteneffekte

    Funktionale Programmiersprachen, insbesondere Haskell, legen grossen Wert darauf, Seiteneffekte zu kontrollieren. Sie unterscheiden zwischen dem pure code und der outside world.

    • Haskell: Monaden erlauben es, imperative Effekte in einem rein funktionalen Programm einzubetten.

    Haskell

    starke und statische Typisierung:

    • While strong, static typing makes Haskell safe, type inference makes it concise.
      • stark: keine implizite Typkonvertierung
      • static: zur Compilezeit
      • inference: Typen werden abgeleitet
        Prelude> let add a b= a+b
        Prelude> :type add
        add :: (Num a) => a -> a -> a

    Bedarfsauswertung

    strict versus non-strict evaluation, outermost versus innermost evaluation, lazy evaluation

    • bei der strengen Auswertung werden die Argumente vor den Funktionen ausgewertet
    • Beispiel für (3 + 5)^2
      • Bedarfsauswertung: (3 + 5)^2 = (3 + 5)*(3 + 5) = 8*8 = 64
      • strenge Auswertung: (3 + 5)^2 = 8 ^ 2 = 8 * 8 = 64
    • die feinen Unterschiede:
      • Haskell:
        Prelude> length([4, 3*2, 1/0])
        3
      • Python:
        >>> len ( [ 4,3*2,1/0])
        Traceback (most recent call last):
        File "<stdin>", line 1, in <module>
        ZeroDivisionError: integer division or modulo by zero

    currying

    Die Technik ist auch unter dem Namen Schönfinkeln bekannt und beschreibt die Umwandlung einer Funktion mit mehreren Argumenten func x y z in mehrere Funktionen mit je einem Argument (((func x) y) z ) (http://de.wikipedia.org/wiki/Currying).
    In Haskell nimmt jede Funktion genau einArgument an. Gerne wird partielle Auswertung irrtümlich als currying bezeichnet.

    • Beispiele:
      • Haskell
        add :: (Num a) => a -> a -> a
        add a b= a+b
        Prelude> add  1 2
        3
        Prelude> let addOne= add 1
        Prelude> addOne 2
        3
      • Python (partielle Auswertung)
        >>> from functools import partial

        >>> def add(a,b): return a+b
        ...
        >>> addOne= partial(add,1)
        >>> addOne(2)
        3
      • C++ (partielle Auswertung)
        #include <iostream>
        #include <boost/bind.hpp>
        #include <boost/function.hpp>
        using boost::bind;

        using boost::function;

        int add(int a, int b){

        return a+b;
        };

        int main(){

        function<int(int)> addOne = bind(add, _1, 1);

        std::cout << "1+2= " << addOne(2) << std::endl;

        }
  •  

    Expression Templates

    Expression Templates
    is a C++ template metaprogramming technique in which templates are used to represent part of an expression. (http://en.wikipedia.org/wiki/Expression_templates)
    Die Technik wurde unabhängig von Todd Veldhuizen und David Vandevoorde entwickelt.

    Domain-Specific-Embedded-Language (DSEL)

    DSEL
    Sprache, die auf eine spezielle Domäne ausgerichtet ist und darüber hinaus in der Gastgebersprache, in diesem Falle C++, eingebettet ist.

    Charkteristiken

    Ermöglicht es mit Hilfe von Template Metaprogramming und Operatur Überladung
    • in der Universalsprache C++ eine mächtige, spezielle Subsprachen für besondere Anwendungsbereiche zu definieren
    • , das sowohl die Gast- wie auch die Gastgebersprache der C++ Syntax genügen (vgl. YACC, antlr, flex oder auch make als Domain-Specific-Languages)

    Klassischer Anwendungsfall: Rechnen mit Matrizen

    Naiver Ansatz: Operator Überladung

    Mittels Operator Überladung lassen sich Vektoroperationen wie Vektor x = b*c + d*esehr natürlich ausdrücken. Die Addition und Multiplikation zweier Vektoren ist schnell implementiert.

    Das Hauptprogramm


    #include <chrono>
    #include <iostream>

    #include "vekUtils.h"
    #include "vektor.h"

    int main(){
    Vektor<int> b(LENGTH);

    Vektor<int> c(LENGTH);
    Vektor<int> d(LENGTH);

    Vektor<int> e(LENGTH);
    for (size_t i=0; i < LENGTH; ++i){

    b[i]=i;
    c[i]=i+i;

    d[i]=i+i+i;
    e[i]=i+i+i+i;

    }
    auto begin= std::chrono::monotonic_clock::now();

    auto x= b*c + d*e;
    auto last= std::chrono::monotonic_clock::now() - begin;

    std::cout << "calculating Vektor<int> x= b*c + d*e; with Vektors of length "
    << LENGTH << " takes " << std::chrono::duration<double>(last).count()
    << " seconds" << std::endl;

    showMe(x,10);
    Vektor<double> f(LENGTH);

    Vektor<double> g(LENGTH);
    double val;
    for (size_t i=0; i < LENGTH; ++i){

    val= 0.01*i;
    f[i]=val;

    g[i]=1+val;
    }
    begin= std::chrono::monotonic_clock::now();

    auto y= f*g;
    last= std::chrono::monotonic_clock::now() - begin;

    std::cout << "calculating Vektor<double> y= f*g; with Vektors of length "
    << LENGTH << " takes " <<
    std::chrono::duration<double>(last).count() << " seconds" << std::endl;

    showMe(y,10);

    }

     

    Der Vektor


    template <typename T>
    class Vektor{
    public:


    Vektor(size_t s): data(new T[s]),dataSize(s){

    for(size_t i=0; i< size(); ++i) data[i]= T();

    }

    ~Vektor(){
    delete[] data;
    }


    Vektor& operator= (Vektor<T> const& other){
    if ( &other != this){

    for (size_t i=0; i<size(); ++i){

    data[i]= other.data[i];
    }
    }

    return *this;
    }

    T operator[] (size_t i) const{

    return data[i];
    }

    T& operator[] (size_t i){

    return data[i];
    }


    size_t size() const{

    return dataSize;
    }

    private:

    T* data;

    size_t dataSize;
    };

     

    Die Arithmetik


    template <typename T>

    Vektor<T> operator+ (Vektor<T> const& a, Vektor<T> const& b){

    Vektor<T> res(a.size());
    for (size_t i=0; i < a.size(); ++i){

    res[i]= a[i] + b[i];

    }
    return res;
    }

    template <typename T>

    Vektor<T> operator* (Vektor<T> const& a, Vektor<T> const& b){

    Vektor<T> res(a.size());
    for (size_t i=0; i < a.size(); ++i){

    res[i]= a[i] * b[i];

    }
    return res;
    }

     

    Das Performanceproblem

    Diese naive Technik besitzt gravierende Nachteile arrowbrightDie Operatoren sind gierig. Dies führt zu unnötigen Erzeugen von temporären Vektoren und überflüssigen Lese- und Schreibezugriffen.
    1. der Ausdruck Vektor x = b*c + d*ebenötigt 3 temporäre Vektoren
      1. temp1= b*c
      2. temp2= d*e
      3. temp3= temp1+temp2
      4. x= temp3
    2. statt dem eimaligen Lesen jedes Vektors b,c,d und e und dem einmaligen Schreiben des Ergebnisses in x müssen 3 temporäre Vektoren zusätzlich geschrieben und gelesen werden

    Idee: Wende lazy evaluation an

    • rolle den Gesamtausdruck aus, bevor die Operatoren angewandt werden
    • werte den Gesamtausdruck erst bei der Ergebniszuweisung aus
    • wende die Vektoroperation x[i] = b[i]*c[i] + d[i]*e[i] elementweise für jeden Index i aus, sodass weder temporäre Vektoren noch zusätzliche Lese- oder Schreiboperationen notwendig sind

    Technik

    Zur Compilezeit wird aus den Expression Template ein Parse Tree erzeugt.
    • Parse Tree:
      ParseTree.png

    Laufzeitvergleich

    Das gleiche Programm zur Vektorarithmethik mit Expression Templates implementiert, bringt einen deutlichenPerformancegewinn. Zur Kontrolle gebe ich jeweils die ersten zehn Werte aus.
    1. klassische Variante
      calculating  Vektor<int> x= b*c + d*e; with Vektors of length 50000000 takes 5.20741 seconds
      0 14 56 126 224 350 504 686 896 1134...
      calculating Vektor<double> y= f*g; with Vektors of length 50000000 takes 9.72478 seconds
      0 0.0101 0.0204 0.0309 0.0416 0.0525 0.0636 0.0749 0.0864 0.0981...
    2. Expression Templates Variante (vermutlich schlägt hier lazy evaluation zu)
      calculating  Vektor<int> x= b*c + d*e; with Vektors of length 50000000 takes 1e-06 seconds
      0 14 56 126 224 350 504 686 896 1134...
      calculating Vektor<double> y= f*g; with Vektors of length 50000000 takes 1e-06 seconds
      0 0.0101 0.0204 0.0309 0.0416 0.0525 0.0636 0.0749 0.0864 0.0981...
    Fazit
    1. Vermeidung temporärer Variablen
      • die reine Arithmetik x= b*c + d*e ist natürlich deutlich schneller, da weder temporäre Vektoren allokiert/deallokiert noch unnötige Lese- und Schreiboperation auf den temporären Vektoren notwendig sind
    2. Compilezeit
      • die beiden Programme besitzten ein ähnliches Compilzeitverhalten
        grimm@laiard ExpressionTemplates $ time g++ -std=c++0x -o vektor vektor.cpp

        real 0m0.373s
        user 0m0.312s
        sys 0m0.056s
        grimm@laiard ExpressionTemplates $ time g++ -std=c++0x -o vektorExpression vektorExpression.cpp

        real 0m0.381s
        user 0m0.320s
        sys 0m0.052s
    3. Laufzeitverhalten
      • die optimierte Arithmetik wirkt sich natürlich auch auf das Laufzeitverhalten des Programmes aus
        grimm@laiard ExpressionTemplates $ time vektor
        calculating Vektor<int> x= b*c + d*e; with Vektors of length 50000000 takes 4.35834 seconds
        0 14 56 126 224 350 504 686 896 1134...
        calculating Vektor<double> y= f*g; with Vektors of length 50000000 takes 11.7362 seconds
        0 0.0101 0.0204 0.0309 0.0416 0.0525 0.0636 0.0749 0.0864 0.0981...

        real 0m48.025s
        user 0m8.877s
        sys 0m2.120s
        grimm@laiard ExpressionTemplates $ time vektorExpression
        calculating Vektor<int> x= b*c + d*e; with Vektors of length 50000000 takes 0 seconds
        0 14 56 126 224 350 504 686 896 1134...
        calculating Vektor<double> y= f*g; with Vektors of length 50000000 takes 1e-06 seconds
        0 0.0101 0.0204 0.0309 0.0416 0.0525 0.0636 0.0749 0.0864 0.0981...

        real 0m5.349s
        user 0m3.612s
        sys 0m1.044s
    Die Vektoroperationen mit Expression Templates sind um den Faktor 9 schneller.

    Expression Templates

    Weder die Anwendung der Arithemtik im Hauptprogramm noch der Vektor selber unterscheiden sich wesentlich. Lediglich die Vektorarithmetik verändert sich entscheidend.

    Das Hauptprogramm

    Das Hauptprogramm, das die Expression Templates anwendet, unterscheidet sich nicht vom dem klassischen Ansatz.


    #include <chrono>
    #include <iostream>
    #include "vekUtils.h"
    #include "vektorExpression.h"

    int main(){
    VektorExpression<int> b(LENGTH);
    VektorExpression<int> c(LENGTH);

    VektorExpression<int> d(LENGTH);
    VektorExpression<int> e(LENGTH);
    for (size_t i=0; i<LENGTH; ++i){

    b[i]=i;
    c[i]=i+i;

    d[i]=i+i+i;
    e[i]=i+i+i+i;

    }
    auto begin= std::chrono::monotonic_clock::now();

    auto x= b*c + d*e;
    auto last= std::chrono::monotonic_clock::now() - begin;

    std::cout << "calculating Vektor<int> x= b*c + d*e; with Vektors of length "
    << LENGTH << "takes " << std::chrono::duration<double>(last).count()
    << " seconds" << std::endl;

    showMe(x,10);
    VektorExpression<double> f(LENGTH);

    VektorExpression<double> g(LENGTH);
    double val;
    for (size_t i=0; i < LENGTH; ++i){

    val= 0.01*i;
    f[i]=val;

    g[i]=1+val;
    }
    begin= std::chrono::monotonic_clock::now();

    auto y= f*g;
    last= std::chrono::monotonic_clock::now() - begin;

    std::cout << "calculating Vektor<double> y= f*g; with Vektors of length "
    << LENGTH << " takes " << std::chrono::duration<double>(last).count()
    << " seconds" << std::endl;

    showMe(y,10);

     

    • durch auto y= f*g nehme ich das Expression Template an, den der exakte Typ ist nicht einfach zu bestimmen

    Der Vektor


    template <typename T>
    class VektorExpression{

    public:

    typedef T ElementType;

    VektorExpression(size_t s): data(new T[s]),dataSize(s){

    for(size_t i=0; i< size(); ++i) data[i]= T();

    }

    ~VektorExpression(){
    delete[] data;
    }


    template <typename Expression>
    VektorExpression<T>& operator= (Expression const& other){

    if ( &other != this){
    for (size_t i=0; i<size(); ++i){

    data[i]= other[i];
    }
    }

    return *this;
    }

    T operator[] (size_t i) const{

    return data[i];
    }

    T& operator[] (size_t i){

    return data[i];
    }

    size_t size() const{

    return dataSize;
    }

    private:

    T* data;

    size_t dataSize;

     

    • der einzige Unterschied zwischen den Typen Vektor und VektorExpression ist, das VektorExpression einen parametrisierten Zuweisungsoperator besitzt, der eine Expression annehmen kann

    Die Arithmetik



    /* the expression tree node */
    /* define the primary template*/
    template <typename Left, typename Operation, typename Right>

    struct Expression{
    Expression( Left const& l, Right const& r): left(l), right(r) {}


    typedef typename Operation::ElementType ElementType;

    ElementType operator[](size_t index) const{

    return Operation::apply(left[index],right[index]);
    }


    Left const& left;
    Right const& right;
    };

    /* the forward declaration of the arithmetic functions*/
    template<typename ET> struct Plus;
    template<typename ET> struct Mult;


    // addition of the expressions
    template <typename Left,typename Right>
    Expression<Left,Plus<typename Left::ElementType>,Right>

    operator+ (Left const& l, Right const& r){

    return Expression<Left,Plus<typename Left::ElementType>,Right>(l,r);

    }

    // multiplication of the expressions
    template <typename Left,typename Right>
    Expression<Left,Mult<typename Left::ElementType>,Right>

    operator* (Left const& l, Right const& r){

    return Expression<Left,Mult<typename Left::ElementType>,Right>(l,r);

    }

    // elementwise addition
    template <typename ET>
    struct Plus{
    typedef ET ElementType;

    static ElementType apply(ElementType l,ElementType r){
    return l+r;

    }
    };

    // elementwise multiplication
    template <typename ET>
    struct Mult{

    typedef ET ElementType;
    static ElementType apply(ElementType l, ElementType r){

    return l*r;
    }
    };

     

    Was geschieht nun zur Compilezeit mit dem Ausdruck x= b*c + d*e:
    1. der Ausdruck b*c + d*ewird ausgerollt
      1. die Multiplikationen werden angewandt operator operator *
        Expression<VektorExpression<double>,Mult<double>,VektorExpression<double> >
      2. die Addition wird auf die Teilausdrücke angewandt operator +
        Expression< Expression<VektorExpression<double>,Mult<double>,VektorExpression<double> > ,
        Plus<double>,
        Expression<VektorExpression<double>,Mult<double>,VektorExpression<double> > >
    2. der Gesamtaudruck wird zugewiesen operator = und stösst die Berechnung der Werte über den Indexoperator operator []an
      • für jeden Index des Vektors wird Operation::apply(left[index],right[index]);ausgerollt und ausgerechnet
        x[index]= b[index]*c[index] + d[index]*e[index]
    Der Datentyp der VektorExpression wird über Plus<typename Left::ElementType> automatisch an die Plus und MultFunktion durchgereicht. Der linke Operand bestimmt das Ergebnis der Operation.

    Anwendungen

    Blitz++

    • von Todd L. Veldhuizen entwickelt
    • die numerische Bibliothek Blitz++ verbindet hoch performante Berechnung von Matrizen und Vektoren mit der komfortablen Ausdrucksweise von Operator Überladung in C++
    Blitz++is a C++ class library for scientific computing which provides performance on par with Fortran 77/90.

    Beispiel

    Arrayausdrücke
    Die arithmetische Operation über Arrays

    #include <iostream>
    #include <blitz/array.h>

    using namespace blitz;

    int main()
    {
    Array<float,2> A(3,3), B(3,3), C(3,3);

    A = 1, 0, 0,
    2, 2, 2,

    1, 0, 0;

    B = 0, 0, 7,

    0, 8, 0,
    9, 9, 9;

    C = A + B;

    std::cout << "A = " << A << std::endl
    << "B = " << B << std::endl
    << "C = " << C << std::endl;

    return 0;
    }

     

    ergibt die folgende Ausgabe
    A = 3 x 3

    [ 1 0 0
    2 2 2
    1 0 0 ]

    B = 3 x 3
    [ 0 0 7
    0 8 0
    9 9 9 ]

    C = 3 x 3
    [ 1 0 7
    2 10 2
    10 9 9 ]
    Vektorausdrücke
    Das einführende Beispiel zu Expression Templates lässt sich natürlich auch in Blitz formulieren.

    include <chrono>
    #include <iostream>
    #include "../vekUtils.h"
    #include <blitz/array.h>

    using namespace blitz;

    int main(){
    Array<int,1> b(LENGTH);

    Array<int,1> c(LENGTH);
    Array<int,1> d(LENGTH);

    Array<int,1> e(LENGTH);
    Array<int,1> x(LENGTH);

    for (int i=0; i<LENGTH; ++i){

    b(i)=i;
    c(i)=i+i;

    d(i)=i+i+i;
    e(i)=i+i+i+i;

    }
    auto begin= std::chrono::monotonic_clock::now();

    x= b*c + d*e;
    auto last= std::chrono::monotonic_clock::now() - begin;

    std::cout << "calculating Vektor<int> x= b*c + d*e; with Vektors of length "
    << LENGTH << " takes " << std::chrono::duration<double>(last).count()
    << " seconds" << std::endl;

    std::cout << x(Range(0,9)) << std::endl;

    Array<double,1> f(LENGTH);
    Array<double,1> g(LENGTH);
    Array<double,1> y(LENGTH);

    double val;
    for (int i=0; i < LENGTH; ++i){

    val= 0.01*i;
    f(i)=val;

    g(i)=1+val;
    }
    begin= std::chrono::monotonic_clock::now();

    y= f*g;
    last= std::chrono::monotonic_clock::now() - begin;

    std::cout << "calculating Vektor<double> y= f*g; with Vektors of length "
    << LENGTH << " takes " << std::chrono::duration<double>(last).count()
    << " seconds" << std::endl;

    std::cout << y(Range(0,9)) << std::endl;

    }

     

    Das Laufzeitverhalten der drei Implementierung:
    1. klassische OO Ansatz
      calculating  Vektor<int> x= b*c + d*e; with Vektors of length 10000000 takes 0.772866 seconds
      0 14 56 126 224 350 504 686 896 1134...
      calculating Vektor<double> y= f*g; with Vektors of length 10000000 takes 0.356094 seconds
      0 0.0101 0.0204 0.0309 0.0416 0.0525 0.0636 0.0749 0.0864 0.0981...

      real 0m2.099s
      user 0m1.676s
      sys 0m0.368s
    2. Expression Template Implementierung
      calculating  Vektor<int> x= b*c + d*e; with Vektors of length 10000000 takes 1e-06 seconds
      0 14 56 126 224 350 504 686 896 1134...
      calculating Vektor<double> y= f*g; with Vektors of length 10000000 takes 0 seconds
      0 0.0101 0.0204 0.0309 0.0416 0.0525 0.0636 0.0749 0.0864 0.0981...

      real 0m0.952s
      user 0m0.740s
      sys 0m0.204s
    3. Blitz++
      calculating  Vektor<int> x= b*c + d*e; with Vektors of length 10000000 takes 0.056804 seconds
      10
      [ 0 14 56 126 224 350 504
      686 896 1134 ]
      calculating Vektor<double> y= f*g; with Vektors of length 10000000 takes 0.081442 seconds
      10
      [ 0 0.0101 0.0204 0.0309 0.0416 0.0525 0.0636
      0.0749 0.0864 0.0981 ]

      real 0m0.483s
      user 0m0.208s
      sys 0m0.268s
    Jede Applikation ist ca. um den Faktor 2 schneller.

    Hauptfeatures Array

    • über Elementtyp, Dimension und Speicherlayout (C versus Fortran) parametrisierbar
    • unterstützt
      • Indexzugriff, Slicing und Unterarrays
      • natürliche Arithmetik mit Expression Templates
      • Reduktion auf einen einzigen Ausgabewert (min, max, mean, count...)

    Optimierungen

    Expression Templates
    Durch das Instanziieren der Templates zur Compilezeit und der damit verbundenen Evaluierung der Templateausdrücke, werden temporäre Variablen und Speicherzugriffe auf das notwenigste reduziert.
    Darüber hinaus können in Blitz++ noch weitergehende Optimierungen angewandt werden, da die arithmetischen Ausdrücke als ganzes im Parse-Tree vorliegen, bevor sie evaluiert werden.
    Vertauschen von Schleifen (loop interchange)

    for (i = 0; i < N1; ++i){
    for (j = 0; j < N2; ++j){
    a[i][j]= b[i][j]*c[i][j];
    }

    }

     


    Für den Prozessor ist es optimal, wenn die Speicherlayout so ist, das die Elemente mit Laufindex k direkt nebeneinander im Speicher liegen. In diesem Fall liest er die eingelesen Cache-Line vom Hauptspeicher direkt verarbeiten. Diese Schleifenstruktur entspricht dem Speicherlayout von C-Programmen, aber nicht den Fortran Programmen. Bei der Loop interchange Optimierung werden die Indizes vertauscht um den Speicherzugriff für den Prozessor zu optimieren. Dies kann einfach durch die Vertauschung der Spalten erreicht werden.


    for (j = 0; j < N2; ++j){
    for (i = 0; i < N1; ++i){
    a[i][j]= b[i][j]*c[i][j];
    }

    }

     

    Extrahieren der Schrittweitenberechnungen (Hoisting stride calculations)
    In einem Ausdruck der Form

    for (int i = 0; i < 100; ++i) {

    // use array[i];
    }

     

    muss der Arrayzugriff array[i] in jeder Iteration durch ((byte*)array)[sizeof(T)*index]ermittelt werden. Das ist teuer. Durch

    int stride = sizeof(T);
    for (int i = 0; i < 100; i += stride) {

    // use array[i];

    }

     

    wird die Multplikation durch eine Addition ersetzt und die Schrittweite im vorraus berechnet. Dadurch ist der Arrayzugriff array[i] deutlich einfacher: ((byte *)array)[i].
    Innere Schleifen auflösen (Collapsing inner loops)
    Blitz löst die innere Schleife auf, falls sie hinreichend klein ist.
    Teilweise auflösen der inneren Spalte (Partial loop unrolling)
    Ist die Schleife zu gross, bietet sich das teilweie auflösen der Schleife an.
    Aus

    for (i=0; i<60; i++) a[i] = a[i] * b + c;

     

    wird somit

    for (i=0; i<60;) {

    a[i++] = a[i] * b + c;

    a[i++] = a[i] * b + c;

    a[i++] = a[i] * b + c;

    a[i++] = a[i] * b + c;

    a[i++] = a[i] * b + c;

    a[i++] = a[i] * b + c;

    }

     

    Schleifenblöcke (Loop tiling, Loop blocking)
    Um mögliche cache misses zu vermeiden und die cache line optimal auszunützen, kann die Iteration über ein 2-dimensionalen Raum in viele Iterationen über kleine Blöcke aufgebrochen werden.
    Aus

    for (i=0; i<N; i++){

    for (j=0; j<N; j++){

    c[i] = c[i]+ a[i,j]*b[j];

    }
    }

     

    wird

    for (i=0; i<N; i+=2){

    for (j=0; j<N; j+=2){

    for (ii=i; ii<min(i+2,N); ii++){

    for (jj=j; jj<min(j+2,N); jj++){

    c[ii] =c[ii]+ a[ii,jj]*b[jj];

    }
    }
    }
    }

     

    Spirit

    Das Spirit Parser Framework als Teile der Boost Bibliotheken ist ein objektorientierter, rekursiv absteigender Parsergenerator, der mittels Templatemetaprogrammierung und insbesondere Expression Templates implementiert worden ist. Damit lässt sich ein Parser als DSEL direkt in C++ formulieren. Dieser Parser versteht die http://de.wikipedia.org/wiki/Erweiterte_Backus-Naur-Form, mit deren Hilfe eine kontextfreie Grammatikformuliert werden kann.

    Module

    Das Parser Framework besteht bietet neben dem Parser noch einen Lexer und einen Ausgabe Erzeuger.
    • Spirit Struktur:
      spiritstructure.png
    • Spirit.Classic: Entspricht Boost Spirit 1.8.x.
    • Spirit.Qi: Rekursive absteigende Parserbibliothek
    • Spirit.Lex: Lexikalischer Analysator
    • Spirit.Karma: Ausgaben Erzeuger

    Syntax

    SyntaxBedeutung
    x >> y x gefolgt von y
    *x x null oder mindestens ein Mal
    x | y x oder y
    +x x mindestens ein Mal
    !x x null oder ein Mal
    x & y x und y
    x - y x aber nicht y
    x ^ y x oder y aber nicht beide zusammen
    ( x ) Angabe der Prioriät
    x % y eine oder mehrere Wiederholungen von x, getrennt durch y
    ~x alles außer x
    x[FunktionsAusdruck] wende den Funktionsausdruck auf x an

    EBNF versus Spirit

    Das Beispiel für eine Grammatik ist ein Calculator.
    EBNF
    group       ::= '(' expression ')'

    factor ::= integer | group
    term ::= factor (('*' factor) | ('/' factor))*
    expression ::= term (('+' term) | ('-' term))*

    Spirit

    group = '(' >> expression >> ')';

    factor = integer | group;
    term = factor >> *(('*' >> factor) | ('/' >> factor));

    expression = term >> *(('+' >> term) | ('-' >> term));

     

    Die Unterschiede sind den Regeln von C++ geschuldet. Dies sind im wesentlichen die Operator Überladung Regeln, die auf äusserster Ebene angewandt werden.
    • ;: '(' >> expression >> ')';
      der Strichpunkt erklärt ein Statement in C++
    • *x: *(('+' >> term) | ('-' >> term))
      der Stern muss vor dem Ausdruck stehen
    • >>: '+' >> term
      der Shift-Operator erklärt die Sequenz von Ausdrücken

    Beispiel: Anzahl der Wörter in einer Datei


    #include <boost/spirit/include/lex_lexertl.hpp>

    #include <boost/spirit/include/qi.hpp>

    #include <fstream>
    #include <iostream>
    #include <map>

    #include <string>

    std::string getLineByLine(char const* inFile) {

    std::string buffer;
    std::string line;
    std::ifstream inStream(inFile);

    if (!inStream.is_open()) {
    std::cerr << "Couldn't open file: " << inFile << std::endl;

    exit(-1);
    }
    while(std::getline(inStream,line))

    buffer += line;
    return buffer;
    }

    struct allWords{

    void operator()(const std::string& key, boost::spirit::qi::unused_type,
    boost::spirit::qi::unused_type) const{

    ++allWords_[key];
    }
    const std::map<std::string,int>& getAllWords() const {

    return allWords_;
    }
    private:
    static std::map<std::string,int> allWords_;

    };
    std::map<std::string,int> allWords::allWords_;

    template <typename Lexer>
    struct wordCountTokens : boost::spirit::lex::lexer<Lexer>{

    wordCountTokens(){
    this->self.add_pattern("WORD", "[^\t\n]+");

    this->self.add_pattern("SPACE","[\t\n]+");
    word = "{WORD}";

    space= "{SPACE}";
    this->self.add(word)

    (space);
    }
    boost::spirit::lex::token_def<std::string> word;

    boost::spirit::lex::token_def<std::string> space;
    };

    template <typename Iterator>
    struct wordCountGrammar : boost::spirit::qi::grammar<Iterator>{

    template <typename TokenDef>
    wordCountGrammar(TokenDef const& tok): wordCountGrammar::base_type(start){

    start = *(tok.word[allWords()]
    |tok.space);

    }
    boost::spirit::qi::rule<Iterator> start;
    };

    int main(int argc, char **argv ){

    typedef boost::spirit::lex::lexertl::token<char const*,
    boost::mpl::vector<std::string> > tokenType;

    typedef boost::spirit::lex::lexertl::lexer<tokenType> lexerType;

    typedef wordCountTokens<lexerType>::iterator_type iteratorType;

    wordCountTokens<lexerType> myLexer;
    wordCountGrammar<iteratorType> myGrammar(myLexer);

    std::string myString= getLineByLine(argv[1]);

    char const* first = myString.c_str();
    char const* last = &first[myString.size()];


    bool r = boost::spirit::lex::tokenize_and_parse(first, last, myLexer, myGrammar);

    allWords myWordCount;
    const std::map<std::string,int> myWords= myWordCount.getAllWords();

    std::map<std::string,int>::const_iterator pos;
    if (r) {
    std::cout << "{";

    for ( pos= myWords.begin(); pos != myWords.end(); ++pos){

    std::cout << "(" << pos->first << "," << pos->second << ")";

    }
    std::cout << "}" << std::endl;
    }

    else {
    std::string rest(first, last);

    std::cerr << "Parsing failed\n" << "stopped at:\"" << rest << "\"\n";

    }
    return 0;
    }

     

    • damit lassen sich die Anzahlt der Wörter in einer Datei zählen
      grimm@laiard wordCount $ ./wordCount /etc/protocols
      {(",2)("A,2)("Aggregation,1)("Assigning,1)("CFTP",,1)("DDN,1)("Datagram,1)("Dissimilar,1)
      ("Encapsulating,1)("EtherIP:,1)("Experimental,1)("Gateway,1)("Host,1)("IP,3)("IP#,1)
      ("ISO,1)("Internet,4)("Internet#,1)("Mobility,1)("Multiplexing,1)
      ("NETBLT:,1)("Next,1)("PUP:,1)("Protocol",,1)("Reliable,1)("ST,1)("STUB",1)
      .
      .
      .
      (there,1)(this,1)(to,1)(updated,1)(use,1)(using,1)(uti,1)(version,2)(with,1)(within,2)(xtp,1)}
    • Anmerkungen:
      • std::string getLineByLine(char const* inFile) liest die ganze Datei ein und gibt sie als String zurück, nachdem die Zeilenendzeichen entfernt wurden
      • struct allWords inkrementiert für jeden neuen Schlüssel in ++allWords_[key] den Zähler
      • struct wordCountTokensdefiniert die Tokens, die mein Lexer an den Parser zurückgibt
        • this->self.add_pattern("WORD", "[^ \t\n]+") beschreibt ein Word
        • this->self.add_pattern("SPACE","[ \t\n]+") beschreibt einen Spaceausdruck
      • struct wordCountGrammar ist der Parser, der eine Regel kennt: *(tok.word[allWords()] | tok.space)
        • tok.word[allWords()] ist die semantische Aktion für das gefundene Wort
        • tok.space für die Leerzeichen existiert keine semantische Aktion
      • boost::spirit::lex::tokenize_and_parse(first, last, myLexer, myGrammar) verbindet den Lexer mit dem Parser und wertet den Tokenstream von [first,last[ aus
      • myWordCount.getAllWords() gibt das Ergebnis der Parserlaufs als Map zurück

    Einführung

    Eine sehr gute Einführung in das neue Spirt.

    Platz, Stelle, Stätte, Baugelände, Gelände, Baustelle, Grundstück, Standort, legen, anlegen [site]

  •  

    IOStreams

    • wurden schon lange vor der Standardisierung von C++ benützt
    • ist ein auf Erweiterbarkeit ausgelegtes Framework
    • erfuhr aber einige Veränderungen zu den alten Stream Klassen
      • Internationalisierung wird unterstützt
      • Charakterstreams (ALERT! deprecated ALERT!) wurden durch Stringsstreams ersetzt
      • Exceptionbehandlung ist integriert
      • IOStream Bibliothek wurden durch Templates implementiert
      • sind im Namensraum std

    Überblick

    • ein Stream Objekt ist ein stream of data mit Eigenschaften, die durch Klassen definiert werden
    • es existiert Eingabe streams class istream und Ausgabe streams class ostream
    • im std Namensraum sind im wesentlichen vier Kanäle definiert
      StreamC ÄquivalentDevicegepuffert
      std::cin stdin Tastatur ja
      std::cout stdout Monitor ja
      std::cerr stderr Monitor nein
      std::clog   Monitor ja
    • die Operatoren >> und <<
      • mittels >> werden die Zeichen auf den Stream geschoben (Inserter)
      • mittels << werden die Zeichen aus dem Stream extrahiert (Extraktor)
    • Manipulatoren sind zum Manipulieren des Streams da
      ManipulatorClassBedeutung
      std::endl ostream Ausgabe von '\n' und leeren des Ausgabepuffers
      std::ends ostream Ausgabe von '\0'
      std::flush ostream Leeren des Ausgabepuffers
      std::ws istream Lesen und Entfernen von whitespace

    Elementare Stream Klassen und Objekte

    IOStreamsGlobal.gif

    ios_base

    • definiert Eigenschaften der Streams, unabhängig vom Charakter Typ und Charakter Traits wie seinen "state" und seine "format flags"
    • es werden chars und wchars ( zwei Chars für Multibyte Charakter Dateien ) als Charaktertypen zur Verfügung gestellt

    basic_ios<>

    • legt die Eigenschaften für alle Streams fest, die vom Charakter Typ und den Charakter Traits abhängen
    • ferner enhält dieser noch einen Streampuffer basis_streambuf<>
    • MOVED TO... Formatierung von Daten

    namespace std{

    template <class charT,
    class traits = char_traits<charT> >

    class basic_ios;
    }
    namespace std{
    typedef basic_ios<char> ios;

    typedef basic_ios<wchar_t> wios;
    }

    basic_istream<>

    • Stream für Lesen von Daten
    • leitet sich virtuell von basic_ios ab

    basic_ostream<>

    • Stream für Lesen von Daten
    • leitet sich virtuell von basic_ios ab

    basic_iostream<>

    • kann sowohl fürs Lesen und Schreiben verwendet werden
    namespace std{

    template <class charT,class traits = char_traits<charT> >
    class basic_istream;

    template <class charT,class traits = char_traits<charT> >
    class basic_ostream;

    template <class charT,class traits = char_traits<charT> >

    class basic_iostream;
    }
    namespace std{
    typedef basic_istream<char> istream;

    typedef basic_istream<wchar_t> wistream;
    typedef basic_ostream<char> ostream;

    typedef basic_ostream<wchar_t> wostream;
    typedef basic_iostream<char> iostream;

    typedef basic_iostream<wchar_t> wiostream;
    }

    basic_streambuf<>

    • MOVED TO... das Interface fürs Lesen und Schreiben von Daten
    • die Schnittstelle zur Erweiterung von IOStreams (Kommunikation mit Sockets,GUIs...)
    • Zusammenhang mit basic_istream
    explicit basic_istream(__streambuf_type* __sb){

    this->init(__sb);
    _M_gcount = streamsize(0);

    }
    • Beispiel zur tee Funktionalität:

    #include <streambuf>

    #include <fstream>
    #include <iostream>
    //teebuf is unbufferd; because of defaultconstruktor of std::streambuf
    class teebuf: public std::streambuf {

    public:
    typedef std::char_traits<char> traits_type;
    typedef traits_type::int_type int_type;

    teebuf(std::streambuf* sb1, std::streambuf* sb2): m_sb1(sb1),m_sb2(sb2) {}

    // overflow will be invoked in case of output
    // sputc sends the char c to the underlying buffer
    virtual int_type overflow(int_type c) {

    if (m_sb1->sputc(c) == traits_type::eof() ||
    m_sb2->sputc(c) == traits_type::eof()) return traits_type::eof();

    return c;
    }
    private:
    std::streambuf* m_sb1;

    std::streambuf* m_sb2;
    };

    int main() {

    std::ofstream logfile("logfile.txt");
    teebuf myTeebuf(std::cout.rdbuf(), logfile.rdbuf());

    std::ostream log(&myTeebuf);

    log << "1" << std::endl;

    log << "2" << std::endl;
    log << "3" << std::endl;

    }

    Globale Stream Objekte

    Neben den Globalen Stream Objekte für chars gibt es noch globale Stream Objekte für wide chars

    StreamC ÄquivalentDevicegepuffert
    std::wcin stdin Tastatur ja
    std::wcout stdout Monitor ja
    std::wcerr stderr Monitor nein
    std::wclog   Monitor ja
    • die vorgestellten C++ Streams sind mit den entsprechenden C Streams synchronisiert
      MOVED TO... man kann einen Stream gleichzeitig mit C++ Streams und C Streams prozessieren (vgl. Bibliotheken)

    Header

    • iosfwd Vorausdeklaration der Stream Klassen
    • streambuf: Streampuffer
    • istream: alles zu basic_istream<> und basic_iostream<>
    • ostream: basic_ostream<>
    • iostream: Deklaration der globale Stream Objekte
      MOVED TO... es sollte meistens ausreichen, in der Header Datei iosfwd zu inkludieren und in der Implementierungsdatei die richtigen Header einzufügen

    Operatoren << und >> für formatierten IO

    Ausgabeoperator <<

    • die Klasse basic_ostream<> definiert den Ausgabeoperator für alle fundamentalen Typen, so zum Beispiel auch für char*, void* und bool
    • << kann natürlich auch für eigenen Typen überladen werden
    • im Gegensatz zur den C Ausgabeoperationen, bei denen man das Format mitgeben muß, werden die auszugebenden Objekte auf die richtige Funktion abgebildet
    • da der Ausgabeoperator immer eine Referenz auf dem Ausgabestream zurückgibt, können Ausgabeoperatoren hintereinander verkettet werden
    • ALERT! die Ausgabereihenfolge der Ausgabeoperators ist von links nach rechts definiert, die seiner Argument aber undefiniert ALERT!
      • daher ist folgendes Programmfragment undefiniert, da kein Sequenzpunkt zwischen den Argumenten liegt:
    int x=5;
    std::cout << --x << ":" << ++x << std::endl;
    • ALERT!falls die Präzedenz des auszugebenden Objekts niedriger ist als die Präzedenz der Ausgabeoperators (entspricht der Präzedenz des Shift Operators), sollte der auszugebende Ausruck geklammert werden
      • dies gilt insbesondere für alle logischen Ausdrücke:
    int a,b;

    std::cout << a == b << std::endl; // wird als ( (std::cout << a) ( == ) ( b << std::endl ) ) interpretiert

    Inputoperator >>

    • verhält sich entsprechend dem Ausgabeoperator
    • per default wird beim Lesen von Daten whitespace übersprungen

    Besondere Typen für die Ein- und Ausgabe:

    bool:

    • per default wird true und false zu 1 und 0 konvertiert
    • Werte, die werder 0 noch 1 sind führen beim Lesen zum Setzten des ==ios::failbit=='s
    • mittels des Setzen des ios::boolalpha Bits, wird der boolsche Wert als true ode false repäsentiert
    • bindet man darüberhinaus noch ein German local zu dem Stream, so bewirkt
    bool b= 1;
    std::cout.imbue( locale("de_DE"));

    std::cout << ios::noboolalpha << b << " == " << ios::boolalpha << b << std::endl;

    die Ausgabe von "1 == richtig"

    char*

    • beim Lesen eines C-String wird wird umgebender whitespace übersprungen
    • um einen Pufferüberlauf zu verhindern, sollte explizit die Anzahl der einzulesenden Zeichen gesetzt werden

    char buffer[81]; // 80 Zeichen und '\0'
    std::cin >> std::setw(80) >> buffer;

     

    void*

    • mittels void kann die Adresse eines Objekts eingelesen und ausgegeben werden
    char* cstring= "hello";

    std::cout << "string \"" << cstring << "\" is located ag adress: " << static_cast <void*>( cstring) << std::endl;

    erzeugt folgende Ausgabe: string "hello" is located at adress: 0x100000018

    Stream Puffer

    • es ist auch möglich direkt vom Streampuffer zu lesen oder zu schreiben MOVED TO... dies ist die wohl schnellste Art mit C++ Mitteln Dateien zu kopieren

    Zustand, State des Streams:

    Konstanten

    • der Stream besitzt immer einen von folgenden vier verschiedenen Zuständen:
      KonstanteBedeutung
      goodbit alles ist in Ordnung; keines der anderen Bits ist gesetzt
      eofbit End-of-file wurde erreicht; Ende einer Eingabesequenz
      failbit Fehler; I/O Aktion war nicht erfolgreich
      badbit "Fatal Error"; undefinierter Zustand
    • goodbit besitzt per Defintion den Wert 0; somit sind alle anderen Bits auf 0 gesetzt
    • ALERT! Aktion mit dem Stream haben dann nur eine Auswirkung, wenn der Stream im goodbit Status ist
    • streambuf.h
    #define _IOS_GOOD   0

    #define _IOS_EOF 1
    #define _IOS_FAIL 2
    #define _IOS_BAD 4
    ......

    enum io_state {
    goodbit = _IOS_GOOD,
    eofbit = _IOS_EOF,

    failbit = _IOS_FAIL,
    badbit = _IOS_BAD };

    eofbit

    Beim Einlesen einzelner Zeichen wird das eofbit gesetzt, falls ein Leseversucht hinterdem letzten gültigen Zeichen stattfindet.

     

    failbit

    • zeigt eine Fehler beim Lesen oder Schreiben von Daten an
      • falscher formatierter Leseversuch eines Zeichens als int
      • Leseversuch über die Datei hinaus
      • öffnen einer Datei schlug fehl
      • ALERT! der Wert einer Variable ist undefiniert, wenn der Leseversuch aus dem Ausgabestream fehlschlug
    • der Stream befindet sich aber noch in definierten Zustand und kann durch das Setzen des goodbits weiterbenützt werden

    eofbif/failbit

    • beim Setzten des eofbits beim zeichenweisen Einlesen wird auch das failbit gesetzt, da die IO-Aktion auch fehlschlug
    • hingegen wird beim Lesen von Zeichensequenzen nur das eofbit gesetzt, da der String "zuEnde" ein gültige Eingabe darstellt
    • gesetzte eofbits haben keine Auswirkung auf Ausgabestreams, jedoch auf Eingabe- und Bidrektionale Streams

    badbit:

    • der Zustand des Streams ist undefiniert und dieser kann daher nicht mehr weiterverwendet werden
      • die Größe des Streampuffers kann aufgrund Speichermangels nicht angepaßt werden
      • Code conversion des Streampuffers schlug fehl
      • eine Komponente des Streams warf eine Exception (vgl: traits, locales)

    Zugriff auf den Zustand des Streams:

    Member MethodeBedeutung
    good() gibt goodbit als boolschen Wert zurück
    eof() gibt eofbit als boolschen Wert zurück
    fail() gibt true zurück, falls ein Fehler auftrat (failbit oder badbit) gesetzt
    bad() gibt badbit als boolschen Wert zurück
    rdstate() gibt den Streamzustand als Variable vom Typ iostate zurück
    clear() setzt alle flags auf 0
    clear(state) setzt alle flags auf 0 und sets danach das flag state
    setstate(state) setzt das flag state
    • es genügt ein Aufruf von stream.fail(), um sich das Auftreten eines Fehler anzeigen zu lassen
    • stream.clear() entspricht stream.setstate(std::ios_base::goodbit) und setzt den Stream wieder in einen integren Zustand
    • mittels Bitoperation kann der Zustand eines Streams explizit modifizieren werden:
    if ( stream.rdstate() & std::ios_base::failbit ){

    stream.clear( stream.rdstate() & ~std::ios_base::failbit);

    }
    • die Flags können auch dazu benützt werden, explizit Exceptions für bestimmte Zustände freizuschalten
    • ALERT! im Gegensatz zu den C Streams ist es mit den C++ Streams nicht möglich bei Auftreten eines Streamfehlers (failbit,eofbit oder badbit) weiter mit dem Stream zu operieren; zuerst muß der Stream in einen guten Zustand gesetzt werden

    Boolsche Ausdrücke

    • zwei Funktionen existieren, um Streams in boolschen Ausdrücken zu benützen
      Member FunktionBedeutung
      operator void* () gibt true zurück, falls kein Fehler auftrat; entspricht fail()
      operator !() gibt true zurück, falls ein Fehler auftrat ; entspricht fail()

    operator void*()

    • Compilermagic, oder wie kommt man von Ausdrücken der Form (std::cin >>x) bzw. (std::cout << x ) nach bool
      1. (std::cin >> x) gibt eine Referenz auf (std::cin) zurück
      2. (std::cin ) wird mittels operator void*() auf eine void* Pointer gecastet, der nur im Zustand std::ios_base::goodbit = NULL ist
      3. der void* Pointer kann mittels Promotion zum Typ bool implizit konvertiert werden

    operator !()

    • in Ausdrücken der Form if ( !(std::cin >> x ) ) wird operator!() mit den entsprechenden cast und Konvertierungen verwendet
      1. aufgrund von Präzedenzen entspricht ( std::cin >> x ) dem Ausdruck ( (std::cin) >> x )
      2. ALERT! (!!std::cin) = (std::cin) ALERT!, da der rechte Ausdruck den Zustand des Streams zurückgibt, also einen boolschen Wert und die rechte Seite eine Stream darstellt
    • TIP um den Fehlerzustand abzufragen, wähle fail(), operator!(), operator void*()
    • TIP um den Fehler von Eingabeoperation abzufragen, wähle !good()

    Exceptions

    • es können zwei Typen von Exceptions auftreten:
      • ios_base::failure, wenn der Stream nicht mehr im goodbit Status ist
      • andere Exceptions, die durch von IOStreams referenzierte Objekte geworfen werden
    • Exceptions wurde in C++ eingeführt, nachdem sich schon die Streams etabliert hatten
    • per default werfen die Streams daher keine Exception
    • jedoch können die Streams explizit für Exceptions zu einem Streamzustand freigeschaltet werden
      Member Funktion Bedeutung
      exceptions(flags) Setzt die Flags, die eine Exception auslösen können
      exceptions() gibt die Flags zurück, die eine Exception auslösen können
    • der Stream strm wirft durch strm.exception(std::ios::failtbit|std::ios::badbit) eine Exception, falls der Zustand des Streams in ==std::ios::failbit oder std::ios::badbit geht oder sich bereits in diesem Fehlerstatus befand
    • unter der Annahme, dass der ursprüngliche Stream nicht im Zustand std::ios::goodbit war, bewirkt das Setzen der alten Bits einen Programmabbruch
    ios::iostate oldState= strm.rdstate(); // strm soll nicht im Zustand std::ios::goodbit sein
    strm.exceptions( std::ios::goodbit ); // sorge für die Integrität des Streams

    strm.exceptions( std::ios::failbit | std::ios::badbit ); // setze explizit die potentiell zu werfenden Exceptions

    ....
    strm.setstate( oldState ); // => Exception wird geworfen
    • strm.exceptions(std::ios::goodbit)bewirkt, dass keine Exceptions geworfen wird
      • TIP setzte failbit und badbit für Ausgabestreams
      • TIP setzte eofbit, failbit und badbit für Eingabestreams

    Standard Input/Output Funktionen für unformatierten IO

    Im Gegensatz zu formatierter Ein/Ausgabe kann mit dem externen Device auch unformatiert kommunizieren werden

    Eingabe

    Zeichensequenzen einlesen

    Member Funktionliest bisAnzahl der Zeichenhängt Terminalsymbol anPufferRückgabe
    get(buf,num) ohne newline und eof bis zu min(num-1,newline,eof ) Zeichen ja C-String istream
    get(buf,num,delim) ohne delim oder eof bis zu min(num-1,delim,eof) Zeichen ja C-String istream
    getline(buf,num) einschließlich newline oder eof bis zu min(num-1,newline,eof) Zeichen ja C-String istream
    getline(buf,num,delim) einschließlich delim oder eof bis zu min(num-1,delim,eof) Zeichen ja C-String istream
    getline(istream, string) einschließlich '\n' lies die ganze Zeile nein std::string istream
    getline(istream,string,delim) einschließlich '\n' oder delim liest bis zu "min( '\n',delim)" nein std::string istream
    read(buf,num) eof num Zeichen nein C-String istream
    readsome(buf,num) eof bis zu min(num,eof) Zeichen nein C-String Anzahl der eingelesen Zeichen

     

    • die get* Funktionen enhalten als Defaultbegrenzer '\n', ausser er wird explizit ein Begrenzer delim angegeben
    • für die C-Style Varianten muß man dafür Sorge, dass genügen Speicher allokiert wurde:
    const int max_size= 100;

    char buf[max_size];
    std::cin.getline(buf,num);

    ...
    std::string buf2;
    getline(std::cin,buf2);

    Zeichen einlesen

    • mittels istream& istream::get(char& c) können einzelne Zeichen vom Stream gelesen werden

    Statusinformationen

    • durch streamsize istream::gcout() const bekommt man die Anzahl der Zeichen, die beim letzten unformatierten Einlesen gelesen wurden inklusive des Begrenzers
    • mittels int istream::peek() ist es möglich, sich das nächste Zeichen anzeigen lassen, das man einlesen würde, ohne es zu extrahieren

    Zeichen ignorieren

    FunktionAnzahlRückgabe
    istream& istream::ignore() überliest nächstes Zeichen istream
    istream& istream::ignore(streamsize count) überliest min(count,eof) Zeichen istream
    istream& istream::ignore(streamsize count, int delim) überliest min(count,delim) Zeichen istream

     

    Überlies den Rest der Zeile
    • std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');

    #include <limits>
    #include <iostream>
    int main(){
    int age = 0;


    while ((std::cout << "How old are you? ") && !(std::cin >> age)) {

    std::cout << "That's not a number; ";
    std::cin.clear(); // set in good state

    std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n'); // flush the buffer

    }

    std::cout << "You are " << age << " years old\n";

    }
    Überlies den Rest von cin
    • cin.ignore( numeric_limits<std::streamsize>::max());

    Zeichen auf den Stream zurückschieben

    • istream& isteam::unget() schiebt das letzte Zeichen auf den Stream zurück
    • istream& istream::putback(char c )
      • schiebt das letzte Zeichen auf den Stream zurück, falls das letzte gelesen Zeichen c war
      • im Fehlerfall wird das badbit gesetzt
    • ALERT! der Standard garantiert nur, daß maximal ein Zeichen zwischen zwei Leseoperationen zurückgeschoben werden darf; jegliche Erweiterung ist "implemtation defined"

    Ausgabe

    • ostream& ostream::put(char c)
      • schreibt das Zeichen c auf den Stream
      • gibt den Stream zurück, dessen Zustand man dann prüfen kann
    • ostream& ostream::write(const char* str, streamsize count)
      • schreibt count Zeichen auf den Stream
      • gibt den Steam zurück, dessen Zustand man dann prüfen kann
      • das Stringendzeichen wird mitgeschrieben
      • falls str weniger als count Zeichen enthält, ist das Verhalten undefiniert
    • ostream& ostream::flush()
      • schreibt die Daten auf das externe Device, indem es den internen Puffer leert

    Formatierte Ein-und Ausgabe

    • Mittels Methoden der Streams oder Objekten, die man auf die Streams schiebt, kann auf das Formatieren und Parsen der Daten Einfluß genommen werden.
    • Obwohl ich im weitern gerne vom formatieren der Daten spreche, meine ich natürlich sowohl das Lesen der Daten als auch das Schreiben der Daten.
    • Die Vorgehensweise, Objekte auf den Stream zu schieben, bezeichnet man als Manipulation des Streams.
    • Beide Möglichkeiten erlauben es, die Formatparameter/Formatflags des Streams zu modifizieren.
    • Alle vordefinierten Formatparmeter können mit beiden Mechanismen angesprochen werden.

    Mittels Memberfunktionen

    #include <string>
    #include <fstream>
    #include <iostream>

    #define Format(A) T << #A << std::endl; A


    int main() {

    std::ofstream T("Format.out");
    Format(int i = 47;)
    Format(float f = 2300114.414159;)

    Format(std::string s = "science-computing";)

    Format(T.setf(std::ios::unitbuf);)

    Format(T.setf(std::ios::showbase);)
    Format(T.setf(std::ios::uppercase | std::ios::showpos);)

    Format(T << i << std::endl;) // Default is dec
    Format(T.setf(std::ios::hex, std::ios::basefield);)

    Format(T << i << std::endl;)
    Format(T.setf(std::ios::oct, std::ios::basefield);)

    Format(T << i << std::endl;)
    Format(T.unsetf(std::ios::showbase);)

    Format(T.setf(std::ios::dec, std::ios::basefield);)

    Format(T.setf(std::ios::left, std::ios::adjustfield);)

    Format(T.fill('0');)
    Format(T << "fill char: " << T.fill() << std::endl;)

    Format(T.width(10);)
    T << i << std::endl;

    Format(T.setf(std::ios::right, std::ios::adjustfield);)

    Format(T.width(10);)
    T << i << std::endl;

    Format(T.setf(std::ios::internal, std::ios::adjustfield);)

    Format(T.width(10);)
    T << i << std::endl;

    Format(T << i << std::endl;) // Without width(10)

    Format(T.unsetf(std::ios::showpos);)

    Format(T.setf(std::ios::showpoint);)
    Format(T << "prec = " << T.precision() << std::endl;)

    Format(T.setf(std::ios::scientific, std::ios::floatfield);)

    Format(T << std::endl << f << std::endl;)
    Format(T.unsetf(std::ios::uppercase);)

    Format(T << std::endl << f << std::endl;)
    Format(T.setf(std::ios::fixed, std::ios::floatfield);)

    Format(T << f << std::endl;)
    Format(T.precision(20);)

    Format(T << "prec = " << T.precision() << std::endl;)

    Format(T << std::endl << f << std::endl;)
    Format(T.setf(std::ios::scientific, std::ios::floatfield);)

    Format(T << std::endl << f << std::endl;)
    Format(T.setf(std::ios::fixed, std::ios::floatfield);)

    Format(T << f << std::endl;)

    Format(T.width(10);)

    T << s << std::endl;
    Format(T.width(40);)

    T << s << std::endl;
    Format(T.setf(std::ios::left, std::ios::adjustfield);)

    Format(T.width(40);)
    T << s << std::endl;

    }

    Mittels Manipulatoren

    #include <string>
    #include <fstream>

    #include <iomanip>
    #include <iostream>
    #define Manipulator(A) trc << #A << std::endl; A


    int main() {
    std::ofstream trc("Manipulator.out");
    Manipulator(int i = 47;)

    Manipulator( float f = 2300114.414159;)
    Manipulator( std::string s = "Is there any more?";)

    Manipulator(trc << std::setiosflags(std::ios::unitbuf
    | std::ios::showbase | std::ios::uppercase
    | std::ios::showpos);)

    Manipulator(trc << i << std::endl;)
    Manipulator(trc << std::hex << i << std::endl;)
    Manipulator(trc << std::oct << i << std::endl;)

    Manipulator( trc << std::left ;)
    Manipulator(trc << std::resetiosflags(std::ios::showbase)
    << std::dec << std::setfill('0');)

    Manipulator(trc << "fill char: " << trc.fill() << std::endl;)

    Manipulator(trc << std::setw(10) << i << std::endl;)

    Manipulator(trc << std::right;)
    Manipulator(trc << std::setw(10) << i << std::endl;)

    Manipulator(trc << std::internal;)
    Manipulator(trc << std::setw(10) << i << std::endl;)

    Manipulator(trc << i << std::endl;) // Without std::setw(10)

    Manipulator( trc << std::resetiosflags(std::ios::showpos)

    << (std::showpoint)
    << "prec = " << trc.precision() << std::endl;)

    Manipulator(trc << std::scientific;)
    Manipulator(trc << f << std::resetiosflags(std::ios::uppercase) << std::endl;)

    Manipulator(trc << std::fixed;)
    Manipulator(trc << f << std::endl;)

    Manipulator(trc << std::setprecision(20);)
    Manipulator(trc << "prec = " << trc.precision() << std::endl;)

    Manipulator(trc << f << std::endl;)
    Manipulator(trc << std::scientific;)

    Manipulator(trc << f << std::endl;)
    Manipulator(trc << std::fixed ;)

    Manipulator(trc << f << std::endl;)

    Manipulator(trc << std::setw(10) << s << std::endl;)

    Manipulator(trc << std::setw(40) << s << std::endl;)

    Manipulator(trc << std::left;)
    Manipulator(trc << std::setw(40) << s << std::endl;)

    }

    Format Flags

    • Die Formatflags sind in der Basisklasse ios_base definiert.
    • Da der ios_basenamespace im ios namespace enthalten ist, können die flags statt über std::ios_base:: auch einfacher über std::ios angesprochen werden.
      • Beispiel: std::ios_base::boolalpha entspricht std::ios::boolalpha
    • Um die Flags einfacher anzusprechen, sind sie in Gruppen eingeteilt.

    Allgemeine Flagmanipulationen

    • Folgende Memberfunktionen stehen zur Verfügung um die Flags zu setzen:
    MemberfunktionBedeutung
    setf(flags) setzt die Flags hinzu und gibt die vorherigen Flagstatus zurück
    setf(flags,mask) setzt die Flags zur Gruppe mask neu und gibt den vorherigen Flagstatus zurück
    unsetf(flags) setzte die Flags zurück
    flags() gibt den Flagstatus zurück
    flags(flags) setzt den Flagstatus flags und gibt den alten Status zurück
    copyfmt(stream) kopiert den Formatstatus von stream
    • Im Header iomanip sind die Manipulator definiert, um die Flags zu modfizieren:
    ManipulatorBedeutung
    std::setiosflags(flags) setzt flags indem die Methode setf(flags) aufgerufen wird
    std::resetioslfags(mask) setzte alle flags der Gruppe mask zurück indem die Methode setf(0,mask) aufgerufen wird

    Boolsche Werte

    • Die textuelle Repräsentation boolscher Werte kann zwischen 0/1 und false/true gewählt werden, wobei 0/1 die Defaulteinstellung darstellt.
    flagManipulatorMemberfunktionDarstellungDefaultStreamtyp
    boolalpha std::boolalpha std::setf(std::ios::boolalpha) false/true nein IO
    boolalpha std::noboolalpha std::unsetf(std::ios::boolalpha) 0/1 ja IO

    Feldbreite, Füllcharakter und Ausrichtung

    • Folgende Methoden erlauben einem die Modfikation der entsprechenden Flags:
    MemberfunktionBedeutungStreamtyp
    width() gibt die aktuelle Feldbreite zurück IO
    width(val) gibt die aktuelle Feldbreite zurück und setzt sie auf val IO
    fill() gibt den aktuellen Füllcharakter zurück O
    fill(c) setzt den Füllcharakter auf c O
    • Ensprechendes ist natürlich auch über Manipulatoren möglich:
    ManipulatorBedeutungStreamtyp
    std::setw(val) setzt die Feldbreite für In/Out to val IO
    std::setfill(c) setzt c als Füllcharakter O
    • Für das Alignment stehen drei flags der Grupppe adjustfield zur Verfügung.
    MaskeFlagBedeutungManipulatorMemberfunktionStreamtyp
    adjustfield left links Ausrichtung std::left std::ios::setf(std::ios::left , std::ios::adjustfield) O
      right rechts Ausrichtung std::right std::ios::setf(std::ios::right , std::ios::adjustfield) O
      internal links Ausrichtung des Vorzeichens und rechts Ausrichtung des Wertes std::internal std::ios::setf( std::ios::internal , std::ios::adjustfield) O
    • Per Default ist besteht das Füllzeichen aus dem Leerzeichen und die Feldbreite ist auf 0 gesetzt.
    • Um das Zusammenspiel von Feldbreite und Ausrichtung zu Verdeutlichung, folgende Tabelle, wobei das Füllzeichen durch . repräsentiert wird:
    Ausrichtungwidth()-420.12"Q"'Q'"1234567"
    left 6 -42... 0.12 Q..... Q..... 1234567
    right 6 ...-42 ..0.12 .....Q .....Q 1234567
    internal 6 -...42 ..0.12 .....Q .....Q 1234567
    • Anmerkungen zur Feldbreite:
      • Die Länge des darzustellenden Wertes überstimmt die Feldbreite.
      • ALERT! Als einziger Flagstatus wird die Feldbreite nach jeder Ein- und Ausgabe zurückgesetzt.
      • TIP Die Feldbreiter kann dazu benützt werden, um die maximale Anzahl der einzulesenden Zeichen zu setzten.
    char buffer[81];

    // read, at most 80 characters
    std::cin >> std::setw( sizeof(buffer) ) >> buffer;

    Positives Vorzeichen und Großbuchstaben bei numerischen Werten

    • Per default werden numerische Werte mit Kleinbuchstaben und ohne positives Vorzeichen ausgegeben.
    • Dies Verhalten kann aber angepasst werden.
    FlagManipulatorMemberfunktionBedeutungStreamtyp
    showpos std::showpos std::setf(std::ios::showpos) stellt postives Vorzeichen dar O
    showpos std::nowhowpos std::unsetf(std::ios::showpos) stellt kein pos. Vorzeichen dar O
    uppercase std::uppercase std::setf(std::ios::uppercase) benützt Großbuchstaben für numerisch Werte O
    uppercase std::nouppercase std::unsetf(std::ios::uppercase) benützt Kleinbuchstaben für numerische Werte O

    Numerische Basis

    • Ganzzahlige Werte werden per default als Dezimalzahlen ausgeben.
    • Beim Einlesen setzen die ersten Zeichen die Basis fest.
      • 0 : oktal
      • 0x: hexadezimal
      • Rest: dezimal
    MaskeflagManipulatorMemberfunktionBedeutungStreamyp
    basefield oct std::oct std::setf(std::ios::oct , std::ios::basefield) schreibt und liest oktal IO
      dec std::dec std::setf(std::ios::dec , std::ios::basefield) schreibt und liest dezimal IO
      dec std::hex std::setf(std::ios::hex , std::ios::basefield) schreibt und liest hexadezimal IO
    • Mittels des flags showbase kann man sich die Basis ausgeben lassen.
    flagManipulatorMemberfunktionBedeutung
    showbase std::showbase std::setf(std::ios::showbase) zeigt numerische Basis an O
      std::noshowbase std::unsetf(std::ios::showbase) keine numerische Basis wird angegeben O

    Fließpunkt Zahlen

    • Default für Fließkommazahlen:
      • die Anzahl der signifikanten Nachkommastellen ist 6
      • führende Nullen von der Dezimalpunkt und/oder folgende Nullen werden nicht ausgegeben
      • der Dezimalpunkt wird, falls möglich, nicht ausgeben
      • falls precision() für die Anzahl der signifikanten Nachkommastellen ausreicht, wird die Zahl dezimal, sonst wissentschaftlich ausgegeben
    • Darüber hinaus kann man die Ausgabe eines Dezimalpunktes und die Genauigkeit der darzustellenden Zahl festsetzen.
    MaskeflagManipulatorMemberfunktionBedeutungStreamtyp
        std::setprecision(val) precision(val) setzt die Genauigkeit der Fließkommazahlen O
          precision() gibt die aktuelle Fließkommagenauigkeit zurück O
      showpoint std::showpoint() std::ios::setf(std::ios::showpoint) schreibt den Dezimalpunkt vor O
      showpoint std::noshowpoint() std::ios::unsetf(std::ios::showpoint) schreibt kein Dezimalpunkt vor O
    floatfield fixed std::fixed std::ios::setf(std::ios::fixed , std::ios::floatfield) dezimale Ausgabe O
    floatfield scientific std::scientific std::ios::setf(std::ios::scientific , std::ios::floatfield) wissentschaftliche Ausgabe O
    • precision() bestimmt die Genauigkeit der signifikaten Stellen nach dem Dezimalpunkt, wobei der Wert gegebenfalls nicht abgeschnitten, sondern gerundet wird.
    • Beispiele:
        precision() 421.0 0.0123456778
      Normal 2 4.2e+02 0.012
        6 421 0.0123457
      mit showpoint 2 4.2e+02 0.012
        6 421.000 0.0123457
      fixed 2 421.00 0.01
        6 421.000000 0.012346
      scientific 2 4.21e+02 1.23e-02
        6 4.210000e+02 1.234568e-02

    Allgemeine Formatangaben:

    flagMemberfunktionManipulatorBedeutungStreamtyp
    skipws setf(std::ios::skipws) std::skipws überspringe führenden whitespace beim Lesen I
    skipws unsetf(std::ios::skipws) std::noskipws überspringe nicht führenden "whitespace" beim Lesen I
    unitbuf setf(std::ios::unitbuf) std::unitbuf leere den Ausgabepuffer nach jeder Schreiboperation O
    unitbuf unsetf(std::ios::unitbuf) std::nounitbuf leere den Ausgabepuffer nicht nach jeder Schreiboperator O
    • per default ist beim Lesen std::ios::skipws gesetzt

    Manipulatoren

    Wie Manipulatoren funktionieren

    • Vereinfachend ausgedrückt, besizt ein Ausgabeoperator folgende Form:
    std::ostream& std::ostream:operator << ( std::ostream& (*op) ( std::ostream& ) ){

    return (*op)(*this);
    }
    • op ist ein Pointer zu einer Funktion, die eine std::ostream als Argument annimmt und wieder zurückgibt
    • eine Funktion, die diesem Interface genügt, kann daher als Manipulator verwendet werden
    std::ostream& std::endl(std::ostream& strm){

    strm.put('\n');
    strm.flush();

    return strm;

    }
    • der Aufruf std::cout << std::endl; wird durch den Shift Operator zu std::endl(std::cout) transformiert
    • tatsächlich hängt die Implementierung von std::endl noch vom Charaktertyp, dem trait und dem Stream Charakter Satz ab
    template<class charT, class traits>
    std::basic_ostream<charT,traits>&

    std::endl(std::basic_ostream<charT,traits>& strm){

    strm.put(strm.widen('\n'));

    strm.flush();

    return strm;
    }
    • strm.widen(c) wandelt char c in denn Charaktertyp des streams um
    • std.narrow(c,def) verhält sich zu widen konträr, indem es den Charakter c des Streams nach char konvertieren will; schlägt dies fehl, so wird def zurückgegeben
    • ALERT!die Definition der Manipulatoren mit Argumenten( z.B.: setw(val) ) ist implentationsspezifisch
      • MOVED TO... die IO Streams können nur durch Manipulatoren ohne Argumente erweitert werden

    Eigene Manipulatoren

    Parameterlose Manipulatoren können unter Wahrung des Interfaces einfach hinzugefügt werden.

    #include <istream>
    #include <fstream>

    #include <limits>
    #include <iostream>

    template <class charT, class traits>

    inline
    std::basic_istream<charT,traits>&
    ignoreLine(std::basic_istream<charT,traits>& strm){

    //skip until end-of-line
    strm.ignore(std::numeric_limits<int>::max(),strm.widen('\n'));

    return strm;
    }

    int main(){

    std::ifstream fileStream("dummy.txt");
    int num= 0;

    while ( fileStream >> ignoreLine ) std::cout << "Zeile: " << ++num << std::endl;

    }
    • ALERT! Eigene Manipulatoren dürfen nicht zum std Namensraum hinzugefügt werden.
    Effektor

    Eine von Schwarz vorgestellte Technik erlaubt es jedoch, Manipulatoren mit Argumenten zu simulieren.

    //Effector.cpp// Jerry Schwarz's "effectors."
    #include <iostream>
    #include <sstream>
    #include <string>


    // Put out a prefix of a string:

    class PrefixString {

    std::string str;

    public:

    PrefixString(const std::string& s, int width) : str(s, 0, width) {}

    friend std::ostream& operator<<(std::ostream& os, const PrefixString& fw) {


    return os << fw.str;
    }

    };

    int main(){

    for ( int i = 1; i <= 10 ; ++i ) std::cout << PrefixString("0123456789",i) << std::endl;

    }

    Memberfunktionen versus Manipulatoren

    Obwohl die Mächtigkeit der Memberfuntionen der Mächtigkeit der Manipulatoren entspricht, gibt es doch einige Unterschiede.

    CharakteristikumMemberfunktionManipulatorBeispiel MemberfunktionBeispiel Manipulator
    Kompaktheit mässig gut std::setf(std::ios::hex, std::ios::basefield) std::hex
    Verkettung nein ja   std::cout << std::hex << i << std::endl;
    Rückgabewert urprüngliche Wert Referenz auf Stream width(10) std::setwidth( 10 )
    Erweiterbarkeit nein ja    

    Erweiterung

    • Boost besitzt eine Formatbibliothek , die die Praktikabilität der Formatstrings mit der Typsicherheit der C++ Streams verbindet.

    Filestreams

    • Die IOStreams sind natürlich mehr als eine Framework, das man nutzen kann, um eigene Streams zu schreiben.
    • So existieren Implementierung neben den globalen Streams für Filestreams und Stringstreams.
    • Auf die dritte Instanziierung durch Charakterstreams werde ich nicht eingehen, da sie deprecated ist.
    • Die Filestreams erweitern die IO Streams um ein paar "konkrete" Klassen.

    image008.gif

    • Entsprechend muß natürlich die Streampuffer Struktur erweitert werden.

    image009.gif

    • Im header iosfwd sind alle typedefs definiert um Schreibarbeit zu sparen.
    namespace std{

    template <class charT, class traits= char_traits<charT> > class basic_ifstream;

    typedef basic_ifstream<char> ifstream;
    typedef basic_ifstream<wchar_t> wifstream;

    template <class charT, class traits= char_traits<charT> > class basic_ofstream;

    typedef basic_ofstream<char> ofstream;
    typedef basic_ofstream<wchar_t> wofstream;

    template <class charT, class traits= char_traits<charT> > class basic_fstream;

    typedef basic_fstream<char> fstream;
    typedef basic_fstream<wchar_t> wfstream;

    template <class charT, class traits= char_traits<charT> > class basic_filebuf;

    typedef basic_filebuf<char> filebuf;
    typedef basic_filebuf<wchar_t> wfilebuf;


    }

     

    • Filestreams folgen dem RAII Idiom.
    • Die Datei wird automatische im Konstruktor des Filestreams geöffnet und im Destruktor geschlossen.
    • Das bedeutet insbesondere, daß die Ressource (Datei) geschlossen wird, wenn der Filestreams out of scope geht.
    • Daher muß der Filestream im Gegensatz zu den C-Streams nicht explizit geschlossen werden.
    • Benötigt man die Datei global, dann sollte der Filestream auf dem Heap angelegt werden.
    std::ofstream* filePtr= new std::ofstream("test.txt");

    ...
    delete filePtr;

     

    #include <string>

    #include <iostream>
    #include <fstream>
    #include <iomanip>
    #include <cstdlib>

    void writeCharsetToFile( const std::string& filename );
    void outputFileCharByChar( const std::string& filename );

    void outputFileByBuffer( const std::string& filename );

    int main(){

    std::string filename("charset.out");

    writeCharsetToFile(filename);
    outputFileCharByChar( filename);

    outputFileByBuffer( filename );

    }

    void writeCharsetToFile( const std::string& filename ){

    std::ofstream file( filename.c_str() );

    if ( !file ) {

    std::cerr << "Can' t open file " << std::endl;
    exit( EXIT_FAILURE );

    }
    for ( int i= 32; i < 256; ++i ){

    file << "value: " << std::setw(3) << i << " "
    << "char : " << static_cast< char >(i) << std::endl;

    }
    // close the file automatically

    }

    void outputFileCharByChar( const std::string& filename ){

    std::ifstream file( filename.c_str() );

    if ( !file ) {

    std::cerr << "Can' t open file " << std::endl;
    exit( EXIT_FAILURE );

    }

    char c;
    while ( file.get(c))std::cout.put(c) ;

    // close the file automatically

    }

    void outputFileByBuffer( const std::string& filename ){

    std::ifstream file( filename.c_str() );

    if ( !file ) {

    std::cerr << "Can' t open file " << std::endl;
    exit( EXIT_FAILURE );

    }

    std::cout << file.rdbuf();

    // close the file automatically

    }

     

    Bei bidrektionalen Streams muß explizit vor dem Kontextwechsel der Puffer geleert werden.

    Dateiflags

    • Mittels Dateiflags kann man den Modus genauer spezifizieren, mit dem man die Datei öffnen will.

      FlagBedeutung
      std::ios::in Öffnen zum Lesen (default für std::ifstream)
      std::ios::out Öffnen zum Schreiben (default für std:ofstream)
      std::ios::app hängt die Charakter immer an die Datei an append
      std::ios::ate Anfangspositons am Ende at end_ der Datei
      std::ios::trunc Löscht den ursprünglichen Dateieinhalt
      std::ios::binary Ersetzt keine spezielle Chrakter

     

    • ALERT! Der Unterschied zwischen std::ios::app and std::ios::ate besteht darin, das bei std::ios::app nur Daten an die Datei angehängt werden können, während bei std::ios::ate nur die Anfangsposition auf dem Ende der Datei steht.
    • Ist std::ios::binary gesetzt, so findet keine plattformabhängige Ersetzung/Interpretation von end of line und end of file statt.
    • Beim Eingabe- und Ausgabefilestream sind per default std::ios::in bzw. std:ios::out gesetzt.
    • Hingegen sind beim bidirektionalen Filestream keine flags per default gesetzt.
    • Schlägt das Öffnen einer Datei fehlt, d.h. wird das failbit gesetzt, kann es auch daran liegen, dass versucht wurde, die Datei mit einer ungültigen Kombination von Flags zu öffnen. So ist zum Beispiel std::ios::trunc | std::ios::app nicht erlaubt.
    • Da die Flags mittels des | Operators verknüpft werden können, lässt sich die C Funktionalität mittels fopen nachbilden.
    • Ich verwende im folgenden bitor für |, da | in Tabelle immer interpretiert wird.

      Flag VerknüpfungBedeutungC Mode
      std::ios::in Lesen (Datei muß existieren) "r"
      std::ios::out Löscht Dateiinhalt und schreibt (Datei wird gegebenfalls erzeugt) "w"
      std::ios::out bitor std::ios::trunc Löscht Dateiinhalt und schreibt (Datei wird gegebenfalls erzeugt) "w"
      std:ios::out bitor std::ios::app Hängt an (Datei wird gegebenfalls erzeugt) "a"
      std::ios::in bitor std::ios::out Lies und schreibt (Datei muss existieren) "r+"
      std::ios::in bitor std::ios::out bitor std::ios::trunc Löscht, liest und schreibt (Datei wird gegebenfalls erzeugt) "w+"
    • Neben dem impliziten Öffnen einer Datei im Konstruktor, kann eine Datei auch explizit mit Memberfunktion geöffnet werden.

      MemberfunktionBedeutung
      open(name) Öffnet eine Datei für einen Stream, benützt den Defaultmodus
      open(name,flags) Öffnet eine Datei für eine Stream, benützt den Defaultmodus
      close() Schließt die Datei des Streams
      is_open() gibt zurück, ob eine Datei geöffnet wurde
    #include <fstream>
    #include <iostream>

    int main(int argc, char* argv[]){

    std::ifstream file;

    for ( int i= 1; i <= argc; ++i){

    file.open(argv[i]);

    char c;

    while ( file.get(c)) std::cout.put(c);

    // the status flags must be cleared to reuse the stream
    file.clear();

    file.close();

    }

    }

     

    • ALERT! Durch das Öffnen der Datei werden die Statusflags nicht zurückgesetzt. Daher müssen explizit die Statusflags zurückgesetzt werden um den Stream wieder nutzen zu können. Im konkreten Fall oben wird das eof-Bit und das failbit auf 0 gesetzt.

    Wahlfreier Zugriff

    • Der C++ Stream (externe Device) erlaubt einen wahlfreien Zugriff.

      KlasseMemberfunktionBedeutung
      basic_istream < > tellg() gibt die Leseposition zurück
        seekg(pos) setzt die Leseposition auf einen absoluten Wert
        seekg(offset,rpos) setzt die Leseposition auf einen relativen Wert
      basic_ostream& <> tellp() gibt die Schreibposition zurück
        seekp(pos) setzt die Schreibposition auf einen absoluten Wert
        seekp(offset,rpos) setzt die Schreibposition auf einen relativen Wert
    • ALERT! Diese Positionierung ist in den globalen Streams nicht definiert.
    • ALERT! tellg() und tellp() geben keine integralen Typ, sondern ein Wert vom Typ std::ios::pos_type zurück, denn die logische und die tatsäche Position können unterschiedlich sein; vgl. (end-of-line)
    • Alternativ zu std::ios::pos_type kann auch std::streampos verwendet werden
    • Um die relative Streamposition zu setzen, kann eine der drei folgenden Konstanten verwendet werden.

      KonstanteBedeutung
      std::ios::beg Position ist relativ zum Beginn
      std::ios::cur Position ist relativ zur aktuellen Position
      std::ios::end Position ist relativ zum Ende
    • Da der Offset ein integraler Typ ist, sind folgende Ausdrücke zulässig.
    file.seekg( 0, std::ios::beg);

    file.seekg(20, std::ios::cur);
    file.seekg(-10,std::ios::end);

     

    • ALERT! Der Zugriff ausserhalb des Streams ist undefiniert.
    #include <iostream>
    #include <fstream>

    #include <string>

    void printFileTwice( const std::string& filename ){

    std::ifstream file( filename.c_str() );

    std::cout << file.rdbuf();

    file.seekg(0);

    std::cout << file.rdbuf() ;

    }

    int main( int argc, char* argv[] ){

    for ( int i= 1; i <= argc; ++i ) printFileTwice( argv[i] );

    }

     

    • ALERT! Ein wesentlicher Unterschied, die Datei direkt über den Streampuffer im Gegensatz zu einer Memberfunktion ausgeben zu lassen, besteht darin, dass ein Streampuffer den Streamzustand nicht ändern kann.

    Stringstreams

    image010.gif

    • Stringstreams sind nicht mit einem externen Device verbunden.
    • Sie nennen sich Stringstreams, da ihr Puffer durch einen String repräsentiert wird.
    • Im Header sstream sind die Stringstreams definiert, hingegen im Header iosfwd werden sie deklariert
    namespace std {

    template<typename _CharT, typename _Traits = char_traits<_CharT>,
    typename _Alloc = allocator<_CharT> >

    class basic_istringstream;
    typedef basic_istringstream<char> istringstream;
    typedef basic_istringstream<wchar_t> wistringstream;

    template<typename _CharT, typename _Traits = char_traits<_CharT>,
    typename _Alloc = allocator<_CharT> >

    class basic_ostringstream;
    typedef basic_ostringstream<char> ostringstream;
    typedef basic_ostringstream<wchar_t> wstringstream;

    template<typename _CharT, typename _Traits = char_traits<_CharT>,
    typename _Alloc = allocator<_CharT> >

    class basic_istringstream;
    typedef basic_stringstream<char> stringstream;
    typedef basic_stringstream<wchar_t> wstringstream;

    template<typename _CharT, typename _Traits = char_traits<_CharT>,
    typename _Alloc = allocator<_CharT> >

    class basic_stringbuf;
    typedef basic_stringbuf<char> stringbuf;
    typedef basic_stringbuf<wchar_t> wstringbuf;

     

    • Zur Kommunikation mit dem Streampuffer/String stehen zwei Methoden zur Verfügung.

      Member FunktionBedeutung
      str() Gibt den Puffer als String zurück
      str(string) Setze den Puffer auf string
    • Beim Kontextwechsel vom Schreiben zum Lesen(Lesen zum Schreiben) muß der Stringpuffer nicht geflusht werden, da hier ja keine Synchronisation mit dem externen Device erforderlich ist.
    • HELP Welche Ausgabe erzeugt folgendes Programm ?
    #include <iostream>
    #include <sstream>

    #include <bitset>

    int main(){

    std::ostringstream os;

    os <<"dec: " << 15 << std::hex << " hex: " << 15 << std::endl;

    std::cout << os.str() << std::endl;

    std::bitset<15> b(5789);
    os << "float: " << 4.67 << " bitset" << b << std::endl;


    os.seekp(0);
    os << "oct: " << std::oct << 15;

    std::cout << os.str() << std::endl;

    }

     

    • die Klassiker:
    #include <iomanip>
    #include <iostream>

    #include <sstream>
    #include <string>

    template < class T > T StringTo( const std::string& source ){

    std::istringstream iss(source);
    T ret;
    iss >> ret;

    return ret;

    }

    template< class T > std::string ToString(const T& n){

    std::ostringstream tmp ;
    tmp << n;
    return tmp.str();

    }

    int main(){

    std::cout << "5 = " << std::string("5") << std::endl;

    std::cout << "5 = " << StringTo< int > ( "5") << std::endl;

    std::cout << "5 + 6 = " << StringTo< int > ( "5") + 6 << std::endl;

    std::string erg( ToString( StringTo< int > ( "5") + 6 ) );
    std::cout << "5 + 6 = " << erg << std::endl;

    std::cout << "5e10: " << std::fixed << StringTo < double > ( "5e10") << std::endl;


    }

     

    • Aufgrund des Funktionsarguments T, kann bei ToString der Templatetyp implizit abgeleitet werden.
    • Will man den Erfolg der Konvertierung prüfen, bietet sich der Rückgabewert, Streamstatus und Exceptions als Strategie an.
      1. Schlägt die Konvertierung fehl, wird ein leerer Strings zurückgegben.
      2. In der Funktion kann man explizit if ( iss.fail() ) { std::cout << Konvertierung schlug fehlt << std::endl; } anwenden.
      3. Am Elegantesten sind hier wohl Exceptions:
    iss.exceptions( std::ios::failbit | std::ios::badbit );

    ....
    try{
    StringTo< int > ("5");
    }

    catch( ... ){
    ...
    }

     

    Eingabe und Ausgabestreams verbinden

    Synchronisations der Streampuffer mittels tie()

    • Um sicherzustellen, dass
    std::cout << "Geben Sie Ihr Alter ein: ";

    std::cin >> numb;

     

    die Ausgabe geschrieben wird, bevor die Eingabe gelesen erwarten wird, muß std::coutzuerst geflush werden werden.

    • Zwei Methoden stehen zur Verfügung um Streams zu synchronisieren.

      MemberfuntionBedeutung
      tie() Gibt eine Zeiger auf den Ausgabestream, der an den Stream gebunden ist.
      tie(std::ostream* stream) Bindet stream and den Stream und gibt den alten gebunden Ausgabestream oder 0 zurück
    • Ein Stream kann höchstens an einen Ausgabestream gebunden sein.
    • Jedoch kann ein Ausgabestreams an mehrere Streams gebunden werden.
    • Folgende Verbindungen sind vordefiniert:
      • std::cin.tie( &std::cout );
      • std::wcin.tie( &std::wcout );==
    • Will man aus Performancegründen die Bindung lösen, so übergibt man als Argument 0 oder NULL.
      • std::cin.tie( static_cast < std::ostream* > (0))
    • Auch das Binden eines Ausgabestreams an einen anderen Ausgabestreams ist möglich.
    • So wird durch==std::cerr.tie( &std::cout )== std::cout geflusht, bevor std::cerr geschrieben wird.

    Streams über den Streampuffer verbinden

    • Mittels der Funktion rdbuf() besizt man Kontrolle über den dem Stream assoziierten Puffer.

      MemberfunktionBedeutung
      rdbuf() Gibt den Zeiger auf den Streampuffer zurück
      rdbuf(streambuf*) Der Stream verwendet nun streambuf* als Streampuffer und gibt den alten Streampuffer zurück
    • Es ist nur sinnvoll, einem Streampuffer mehreren Streams zur Verfügung zu stellen. Denn für einen Streams sukzessive neue Puffer zu registrieren ist aufgrund der Pufferung des Streams deutlich komplexer als einen neuen Stream zu defininieren.
    #include <iostream>
    #include <fstream>

    int main(){

    std::ostream hexout( std::cout.rdbuf() );

    hexout.setf( std::ios::hex, std::ios::basefield );

    hexout.setf( std::ios::showbase );

    hexout << "hexout: " << 177 << " " ;

    std::cout << "cout: " << 177 << " " ;
    hexout << "hexout: " << -49 << " " ;

    std::cout << "cout: " << -49 << " " ;
    std::cout << std::endl;

    }

     

    Streams umleiten

    • Der Streampuffer kann dazu benützt werden Streams umzuleiten.
    • Hierzu kann es nötig sein, mittels der Methode copyfmt(stream*) die Formatflags des neuen Streams zu setzen.
    #include <iostream>
    #include <fstream>

    void redirect( std::ostream& strm ){

    std::ofstream file("redirect.txt");

    std::streambuf* strm_buffer= strm.rdbuf();


    strm.rdbuf( file.rdbuf() );

    file << "One row for the file : " << std::endl;

    strm << "One row for the stream: " << std::endl;

    strm.rdbuf( strm_buffer );

    }

    int main(){

    std::cout << "The first row : " << std::endl;


    redirect( std::cout );

    std::cout << "The second row: " << std::endl;

    }

     

    Lebenszeit von Streampuffern

    ALERT!Faustregel: Die Lebenszeit des Streampuffer hängt von der Lebenszeit seines Erzeuger ( Streams ) ab.

    • Geht der Stream out of scope, so auch sein Streampuffer.
    • Dies sollte man als konsequente Umsetzung des RAII Idioms ansehen.
    • Da der Stream nicht der Erzeuger eines über rdbuf() zugeordneten Puffers ist, destruiert er auch nicht diesen Puffer, falls er out of scopegeht.
      • Daraus ergibt sich für RedirectStream:
        • Das Programm besässe undefiniertes Vehalten, falls der alte Puffer in der letzten Zeile von redirect() nicht wieder restauriert werden würde. Einerseits gäbe es in diesem Szenario kein externes device, dem strm(std::cout) zugeordent wäre. Anderseits würde beim automatischen destruieren des globalen Streams nach main () das flushen des Puffers eine Zugriffsverletzung ergeben.
    • Das folgende kleine Programmfragment bringt es nochmals auf den Punkt:
    {
    std::ofstream file("cout.txt");
    std::cout.rdbuf( file.rdbuf() );

    }

     

    MOVED TO... file geht out of scope MOVED TO... file.rdbuf() wird destruiert MOVED TO........

    • Teilen sich mehrere Streams einen Puffer, so muß der den Puffer erzeugende Stream alle anderen Streams überleben.
    std::ofstream file1("file1.txt");
    {
    std::ofstream file2( "file2.txt");

    file1.rdbuf( file2.rdbuf() );
    }

     

    MOVED TO... file2 geht out of scope MOVED TO... file2.rdbuf() wird destruiert MOVED TO........

    • Nur insofern man einen eigenen Puffer erzeugt und diesem einem Stream mittels rdbuf() zuweist, muß man dieses Puffer auch wieder destruieren.
    • TIP Der Stream stream vom Typ basic_iostream hält sich einen Pointer auf seine erzeugten Puffer in stream.basic_iostream.rdbuf(). Dieser unterscheidet sich vom dem Pointer auf stream.rdbuf(), denn wir mit unseren obigen BeispieleStreamBufferUnbuffered immer referenziert haben. Diese beiden Pointer zeigen beim konstruieren des Streams auf den gleichen Puffer. Durch Aufruf von stream.rdbuf("Zeiger auf neuen Puffer") wird nur stream.rdbuf() modifiziert. ALERT! Der Stream kann daher zwei Puffer referenzieren, wobei als Interface zum Stream klassischerweise strm.rdbuf(... ) angesehen wird.

    Bidrektionale Streams:

    • Ein Stream vom Typ std::fstream kann gleichzeitig zum Lesen oder Schreiben verwendet werden.
    • Sein Modus wird über die Flags gesetzt.
    • Will man ein Stream zum Lesen und Schreiben verwenden, muß man den Streampuffer vor dem Kontextswitch leeren. Unterläßt man dies, erhält man undefiniertes Verhalten.
    • Der Streampuffer kann explizit durch Flushen oder implizit durch Setzen der Streamposition geleert werden.
    • Eine Ausnahme dieser Regel stellt der Kontextswitch von Lesen zum Schreiben dar, falls die ganze Datei gelesen wurde.

     

    #include <iostream>
    #include <fstream>
    #include <string>

    int main(){

    std::fstream fstr("InOutIn.txt", std::ios_base::in | std::ios_base::out );

    std::string helloWorld="hello world";
    std::string helloWorld2= "; append hello World";

    fstr << helloWorld ;
    if ( !fstr.good() ) std::cout << "stream status1: " << fstr.rdstate() << std::endl;

    // flush the buffer
    fstr.seekg(0, std::ios_base::beg);

    if ( !fstr.good() ) std::cout << "stream status2: " << fstr.rdstate() << std::endl;



    std::string hello;
    getline( fstr, hello );

    std::cout << hello << std::endl;

    if ( !fstr.good() ){

    std::cout << "stream status3: " << fstr.rdstate() << std::endl;

    fstr.clear();

    }

    fstr << helloWorld2;

    if ( !fstr.good() ) std::cout << "stream status4: " << fstr.rdstate() << std::endl;

    }

     

    Ein- und Ausgabe von eigenen Datentypen

    • Die Ein- und Ausgabe von eigenen Datentypen soll dem Verhalten der Standard Datentypen nachempfunden werden.
    • Die Mächtigkeit von C++ besteht gerade darin, die Ausgabe- Eingabeoperatoren für eigene Datentypen zu überladen.
    • Folgender Datentyp soll daher exemplarisch streamfähig gemacht werden.
    class Fraction{

    public:
    Fraction( int num, int denom= 0): numerator_(num), denominator_(denom){};

    private:
    int numerator_;
    int denominator_;
    };

     

    Ausgabeoperator:

    • Dazu müssen wir den Shiftoperator << implementieren, sodass folgender Ausdruck unterstützt wird: = stream << object <<=
    • Entsprechend der C++ Syntax führt dies zu folgender Evaluierung:
      1. stream.operator << (object) für Standard Datentypen
      2. operator << (stream,object) für eigene Datentypen
    • Da der std Namensraum für Erweiterungen abgeschlossen ist, gilt dies natürlich insbesondere für die Streams. Insofern steht nur die zweite Variante zur Verfügung.
    • Als ersten Ansatz definieren wir einen globalen Operator << für die Klasse Fraction.
    • Zusätzlich benötigt Fraction noch zwei Zugriffsfunktionen, wollen wir nicht die globale Funktione zum friend erklären.
    class Fraction{
    ...

    int getNumerator() const{ return numerator_ ;}
    int getDenominator() const{ return denominator_ ;}

    ...
    };
    inline std::ostream& operator << ( std::ostream& strm, const Fraction& f ){

    strm << f.getNumerator() << "/" << f.getDenominator();

    return strm;
    }

     

    Die Implementierung besitzt drei Nachteile:

    • Fraction Objekte können nur von std::ostream ausgegeben werden.
    • Falls die Feldbreite gesetzt wird, wirkt diese auf den ersten Shiftoperator, da width() nach jeder Ein- und Ausgabeoperation auf 0 gesetzt wird.
    Fraction vat_( 16,100 );

    std::cout << "VAT : " << std::left << std::setw(10) << vat_ << "END" << std::endl;

     

    erzeugt:

    VAT : 16    /100END

    im Gegensatz zu:

    VAT : 16/100    END
    • Mittels "get" Methoden müssen die Variablen umständlich referenziert werden.

    Die ersten beiden Punkte lassen sich durch den neuen Ausgabeoperator lösen, den letzten Punkt werde ich später behandeln:

    template <class charT, class traits>
    inline

    std::basic_ostream<charT,traits>&
    operator << ( std::basic_ostream<charT,traits>& strm,

    const Fraction& f ){

    std::basic_ostringstream<charT,traits> s;

    s.copyfmt(strm);
    s.width(0);

    s << f.getNumerator() << "/" << f.getDenominator();

    strm << s.str();

    return strm;
    }

     

    • durch s.copyfmt(strm) ; s.width(0) ; werden die Formatflags von strm an den Stringstream s explizit mit Breite 0 übertragen
    • das Füllen des Stringstreams geschieht daher mit width(0)
    • lediglich die ursprünglich gesetzte Breite wirkt nun auf den (String-)Ausdruck strm << s.str()
    • ALERT!die Ausgabe ist nun von der Form
      16/100  END 

    Eingabeoperator

    • Obwohl der Eingabeoperator nach den gleichen Prinzipien wie der Ausgabeoperator erfolgt, müssen hier Lesefehler berücksichtigt werden, will man das Verhalten der vordefinierten Eingabeoperatoren nachprogrammieren.
    • Ein erster Ansatz könnte von folgender Form sein:
    inline
    std::istream& operator >> (std::istream& strm, Fraction& f){


    int n,d;

    strm >> n ;
    strm.ignore(); // skip '/'

    strm >> d;

    f= Fraction(n,d);

    return strm;
    }

     

    • Im zweiten und stabileren Ansatz werden nun folgende Punkte berücksichtigt:
      1. die Implementierung hängt nicht mehr vom Charaktertyp char ab
      2. es wird geprüft, ob das Zeichen zwischen den zwei Zahlen ein '/' ist
      3. ganze Zahlen n werden als 'n/1' interpretiert
      4. der Nenner 0 führt zu einem Setzen des std::ios::failbit Flags
      5. ein gültiger Bruch wird nur dann zurückgegeben, wenn alle Leseoperationen erfolgreich waren
    • Gerade die letzten zwei Punkte entsprechen dem Verhalten der Standardeingabeoperatoren, denn
      1. fehlerhafte Eingabe soll schon im Operator berücksichtigt werden
      2. Einleseoperation sind insofernt atomar, daß sie entweder erfolgreich sind oder keine Auswirkung haben
    template < class chatT, class traits > 

    inline
    std::basic_istream < charT , traits& >
    operator >> ( std::basic_istream < charT , traits >& strm, Fraction& f ){

    int n,d;

    strm >> n;

    if ( strm.peek() == '/' ){

    strm.ignore();
    strm >> d;

    }
    else d= 1;

    if ( d == 0 ){

    strm.setstate( std::ios::failbit );

    return strm;

    }

    if ( strm ) f= Fraction(n,d);

    return strm;

     

    • Ein paar Feinheiten sind hier noch nicht berücksichtigt, so wird der Ausdruck 3/ 4 (Leerzeichen vor der 4) als 3/4 interpretiert, hingegen führt 3 /4 ( Leerzeichen nach der 3 ) zu einem Lesefehler

    Ableitungshierachien

    • Eine gängige Implementierung, um globalen Funktionen Zugriff auf private Daten oder Methoden zu erlauben, ist es, sie als friend zu deklarieren.
    • Für einen Ausgabeoperator könnte das folgendermassen aussehen:
    #include <iostream>

    #include <string>

    class Base{

    public:

    Base(): name_("Base"){}

    private:

    friend std::ostream& operator << ( std::ostream& , const Base& );

    std::string name_;

    };


    std::ostream operator << ( std::ostream& strm, const Base& b ){

    strm << "Mein Name ist: " << b.name_ << std::endl;

    return strm;

    }

    int main(){

    Base* b1= new Base;
    std::cout << *b1 << std::endl;

     

    • Ausgabe:
      Mein Name ist: Base
    • Will man seine Klassenstruktur um Derived erweitern, liefert folgende Ansatz leider nicht das gewünschte Resultat.
    #include <iostream>
    #include <string>

    class Base{

    public:

    Base(): name_("Base"){}

    private:

    friend std::ostream& operator << ( std::ostream& , const Base& );

    std::string name_;

    };

    class Derived: public Base{

    public:

    Derived(): name_("Derived"){}

    private:
    std::string name_;

    };

    std::ostream& operator << ( std::ostream& strm, const Base& b ){

    strm << "Mein Name ist: " << b.name_ << std::endl;

    return strm;

    }

    int main(){

    Base* b1= new Base;
    Base* b2= new Derived;

    std::cout << *b1 << std::endl;
    std::cout << *b2 << std::endl;

     

    • Ausgabe:
      Mein Name ist: Base
      Mein Name ist: Base
    • Statische Bindung (nicht virtuell) führt nicht zu gewünschten Ergebnis und dynamische Bindung (virtuell) ist nicht möglich.
    • Da friend Funktionen nicht virtuell sein dürfen virtual functions cannot be friends, muß man einen anderen Ansatz wählen.
    • Wäre "virtuelle Freundchaft" möglich, wäre dies ein Aufbruch der Kapslung der Klassenhierachie.
    • Lösung:
    • TIPTrenne das Interface von der Implementierung:
      • Das Interface (Ausgabeoperator) wird als globale Funktion zur Verfügung gestellt.
      • Die Implementierung wird als virtuelle Methode in der Klassenhierachie definiert und eventuell überschrieben.
    #include <iostream>
    #include <string>

    class Base{

    public:

    Base(): name_("Base"){}

    virtual std::string getName() const { return name_; }

    private:

    std::string name_;

    };

    class Derived: public Base{

    public:

    Derived(): name_("Derived"){}

    virtual std::string getName() const { return name_; }

    private:

    std::string name_;

    };

    std::ostream& operator << ( std::ostream& strm, const Base& b ){

    strm << "Mein Name ist: " << b.getName() << std::endl;

    return strm;

    }

    int main(){

    Base* b1= new Base;
    Base* b2= new Derived;

    std::cout << *b1 << std::endl;
    std::cout << *b2 << std::endl;

     

    • Ausgabe:
      Mein Name ist: Base
      Mein Name ist: Derived
    • Eine Implementierung für die Klasse Fraction sollte daher folgende Struktur besitzen:
    class Fraction{
    ...
    public:
    virtual void printOn( std::ostream& strm) const;

    virtual void scanFrom( std::istream& strm);
    ...

    };

    std::ostream& operator << ( std::ostream& strm, const Fraction& f ){

    f.printOn( strm) ;
    return strm;

    }

    std::istream& operator >> ( std::istream& strm, Fraction& f ){

    f.scanFrom( strm );
    retunr strm;
    }

    void Fraction::scanFrom( std::istream& strm){

    ...
    numerator= n;
    denominator= d;

    }

     

    • Hier übernehmen printOn und scanFrom die Implementierung der Ein- und Ausgabe von Fraction.
    • Da sie Member von Fraction sind, können Sie direkt die privaten Variablen der Klasse referenzieren.
    • Dieses Strukturprinzip ist unter dem Namen NVI (Non Virtual Interface) Idiom bekannt und stellt eine Variante des Schablonenpatterns (Überblick/Motivation) dar. Das NVI geht auf Herb Sutter zurück. Die Prinzipien des NVI sind:
      • Die Basisklasse legt das Interface für die Operationen auf der Klassenstruktur fest.
      • Die Implementierung wird an virtuelle Methoden delegiert.
      • Da auf die Objekte über das Interface der Basisklasse zugegriffen wird, können die virtuellen Funktionen protected oder private deklariert werden.
        • protected, falls für die virtuellen Funktionen eine Defaultimplemtierung vorgesehen ist
        • private, falls die virtuellen Funktionen in jeder abgeleiteten Klasse implementiert werden sollen
        • ALERT! pure virtuelle Funktionen sind hier nicht möglich, da die Basisklasse eine konkrete Klasse sein muß
    • Durch die Trennung von Interface und Implementierung besitzt die Klasse ein sehr stabiles Interface, denn Änderungen/Überschreibungen können auf den virtuellen Methoden implementiert werden.

    Templates:

    • Ein häufiges Problem bei Templates und friend Funktionen verdeutlicht folgender Code:
    #include <iostream>

    template<typename T>
    class Foo {

    public:
    Foo( const T& value );
    friend std::ostream& operator<< (std::ostream& o, const Foo<T>& x);

    private:
    T value_;
    };

    template<typename T>

    Foo<T>::Foo(const T& value)
    : value_(value)

    { }


    template<typename T>
    std::ostream& operator<< (std::ostream& o, const Foo<T>& x)

    { return o << x.value_; }

    int main(){

    Foo<int> var(1);
    std::cout << "value : " << var << std::endl;

     

    • Mein gcc ( 3.3.3 ) quittiert dies mit folgender erklärenden Fehlermeldung:
      Template.cpp:7: warning: friend declaration `std::ostream&
      operator<<(std::ostream, const Foo<T>&)' declares a non-template function
      Template.cpp:7: warning: (if this is not what you intended, make sure the
      function template has already been declared and add <> after the function
      name here) -Wno-non-template-friend disables this warning
      /tmp/ccfDT2Ti.o(.text+0x4a): In function `main':
      : undefined reference to `operator<<(std::basic_ostream<char, std::char_traits<char> >&, Foo<int> const&)'

    • Es wird also ein nicht Template Funktion deklariert und eine Template Funktion definiert.
    • Die Lösung besteht darin, den Compiler beim Parsen der Templatedefinition davon zu überzeugen, daß der operator << selbst ein Template ist. Hierzu bieten sich drei Möglichkeiten an:
    • die Template friend Funktion vorwärts zu deklarieren:
    template<typename T> class Foo;  // pre-declare the template class itself
    template<typename T> std::ostream& operator<< (std::ostream& o, const Foo<T>& x);

    // define the template class
    template<typename T>
    class Foo {
    public:

    Foo( const T& value );
    ....

     

    • die Template friend Funktion explizt als Templatefunktion auszuzeichnen (Empfehlung des gcc)
    class Foo {
    public:
    Foo( const T& value );

    ....
    friend std::ostream& operator<< <> (std::ostream& o, const Foo<T>& x);

    ...
    };

     

    • die Funktion explizit im Templatekörper zu definieren
    class Foo {

    public:
    Foo( const T& value );
    ....

    friend std::ostream& operator<< (std::ostream& o, const Foo<T>& x){

    }
    ...
    };

     

    Regeln zum Überladen der Ein- und Ausgabeoperatoren

    • das Ausgabeformat sollt durch den Eingabeoperator ohne Informationsverlust lesbar sein
    • die Formatspezifikationen sollen berücksichtigt werden (vgl. width())
    • falls ein Fehler auftritt, sollte der entsprechende Zustand gesetzt werden
    • im Fehlerfall (beim Einlesen) sollte das Objekt nicht modifiziert werden
    • die Ausgabe sollte nicht mit newline abgeschlossen werden (vgl. mehrere Ausgaben in einer Zeile)
    • falls ein Formatfehler vorliegt, sollte keine Zeichen eingelesen werden

    Streampuffer

    • die Memberfunktion rdbuf() des Streams liefert ein Zeiger auf den Streampuffer zurück
    • die Streampuffer sind nach dem Non Virtual Interface ( NVI )Prinzip konstruiert
      • public, nichtvirtuelle Methoden legen das stabile Interface zu den Streampuffer fest
      • private, virtuelle Methoden dienen als definierte Schnittstellen, um die Streampuffer durch Ableiten anzupassen

    Als Anwender

    Ausgabe

    • auf den Streampuffer kann man entweder ein oder mehrere Zeichen schreiben

      MemberfunktionBedeutungRückgabewert
      sputc(c) Schickt c an den Streampuffer traits_type:to_int_type(c) oder traits_type::eof()
      sputn(s,n) Schickt n Zeichen der Sequenz const char_type* s an den Streampuffer Anzahl der geschriebenen Zeichen
    • traits_type::int_type versus traits_type::char_type
      • traits_type::char_type repräsentiert den Zeichentyp, mit dem der Streampuffer instanziert wurde -in der Regel char
      • traits_type::int_type besteht aus traits_type::char_type und traits_type::eof()
      • muss traits_type::eof() als Zeichentyp berücksichtigt werden, werden die Zeichen als Elemente vom Typ traits_type::int_type interpretiert, sonst als traits_type::char_type
        • beim Einlesen einer Datei muss traits_type::eof() berücksichtigt werden MOVED TO... traits_type::int_type
        • der Streampuffer stellt alle Zeichen ohne traits_type::eof() zur Verfügung MOVED TO... traits_type::char_type
      • um die kontextabhängige Sicht auf den Zeichentyp zu unterstützen, existieren die zwei Konvertierungsmethoden to_int_type() und to_char_type()
      • char enspricht int und wchar_t entspricht wint_t

    Eingabe

    • das Interface, um vom Streampuffer zu lesen, ist deutlich komplexer, da verschieden Operationen unterstützt werden
    • folgende Operationen beziehen sich immer auf den Streampuffer und nicht auf das dem Streampuffer zugeordnete externe Device

      MemberfunktionBedeutung
      in_avail() gibt die Menge der Zeichen zurück, die minimal verfügbar sind
      sgetc() gibt das aktuelle Zeichen zurück, ohne es zu konsumieren
      sbumpc() gibt das aktuelle Zeichen und konsumiert es
      snextc() Konsumiert das aktuelle Zeichen und gibt das nächste Zeichen zurück
      sgetn(b,n) liest n Zeichen und speichert sie im Zeichenpuffer b
      sputback(c) gibt das Zeichen c an den Streampuffer zurück
      sungetc() springt zum vorherigen Zeichen
    • falls kein Zeichen gelesen werden konnte, geben die drei Funktionen sbumpc(), snextc() und sgetn(b,n) traits_type::eof() zurück

    Locales und Pufferposition

    MemberfunktionBedeutung
    pubimbue(loc) bindet das Local loc an den Streampuffer
    getloc() gibt das aktuelle Local zurück
    pubseekpos(pos) setzt die aktuelle Position fürs Lesen und Schreiben auf die absolute Postition pos
    pubseekpos(pos,modus) analog zu pubseekpos(pos) abhängig vom Modus (Lesen/Schreiben) des Streampuffers
    pubseekoff(offset,rpos) relatives Setzten bezüglich rpos des aktuellen Streamposition
    pubseekoff(offset,rpos,modus) analog zu pubseeekoff(offset,rpos), nur abhängig vom Streammodus
    pubsetbuf(b,n) schreibe n Zeichen aus b auf den Streampuffer
    • modus kann vom Wert ios_base::out oder ios_base::in sein
    • rpos kann vom Wert ios_base::begin, ios_base::end oder ios_base::cur sein
    • die Methode pubsetbuf(b,n) ist nur definiert, wenn der Streampuffer noch nicht benutzt wurde
    • pubsetbuf(0,0) setzt für Filestreampuffer einen ungepufferten Puffer

    Iteratoren

    • mittels std::ostreambuf_iterator(dest) erhält man einen Ausgabestreampuffer Iterator, wobei dest vom Typ Ausgabestream oder Zeiger auf einen Ausgabepuffer sein muss
    • durch diesen Iterator läßt sich der Streampuffer in den STL Algorithmen verwenden
    #include <algorithm>       
    #include <iostream>
    #include <string>

    int main(){
    std::ostreambuf_iterator<char> bufWriter( std::cout );

    std::string hello("hello, world\n");
    std::replace_copy( hello.begin(), hello.end(), bufWriter, 'h','H' ) ;

    }

     

    • Ausgabe:
      Hello, world  
    • ensprechend erzeugt std::istreambuf_iterator(source) einen Eingabestreampuffer, der das erste Zeichen liest
    • durch std::istreambuf () erhält man einen end-of-stream Iterator; d.h. std::ifstreambuf ()-1 ist das letzte Element im zum iterierenden Inputpuffer
    • das klassische Filter Framework, das alle eingelesen Zeichen von std::cin nach std::cout schreibt, läßt sich mittels Streamfunktionalität oder direkt mit dem Streampufferiteratoren implementieren:
    • Stream Variante
    #include <iostream>

    int main(){


    char c;

    while( std::cin.get(c)) std::cout.put(c);

    }

     

    • Iterator Variante
    #include <iostream>

    #include <iterator>

    int main(){

    std::istreambuf_iterator<char> inpos( std::cin );

    std::istreambuf_iterator<char> endpos;

    std::ostreambuf_iterator<char> outpos( std::cout );

    while( inpos != endpos ){

    *outpos= *inpos;

    ++inpos;
    ++outpos;

    }

    }

     

    • da die Iterator Variante direkt auf den Streampuffern arbeitet, ist sie natürlich performanter

    Do it yourself

    Der Einfachheit halber werde ich mich auf den Charaktertyp char beschränken.

    • Streampuffer lassen sich am einfachsten durch ein Bild darstellen

      sbbuffers.gif

    Ausgabepuffer

    • der Ausgabepuffer wird durch drei Zeiger prozessiert, wobei
      • pbase() put base den Anfang
      • pptr() put pointer die aktuelle Position
      • epptr() end put pointer eine Postition nach dem letzten Zeichen liefert
    • beim Schreiben auf des Puffers mit der Funktionen sputc() bzw. sputn() werden die Zeichen auf den Puffer geschoben, bis pptr() == epptr()
    • nun wird die virtuelle, nicht öffentliche Methode overflow() des Streampuffers aufgerufen, damit die Zeichen auf das externe Device geschrieben werden können
    • der Puffer ist nun wieder leer und es gilt pbase() == pptr()
    • will man den Puffer explizit mit dem externen Device synchronisieren (flushen), so wird die nicht öffentliche, virtuelle Methoden sync() aufgerufen
    • die zwei Methoden underflow() und sync() sind aus Streampuffersicht die Methoden, die wir überladen müssen, um einen neuen Streampuffer zu implementiern
    • falls der Streampuffer degeniert ist, d.h. nur aus einem Zeichen besteht, bedeutet jeden Schreiben auf ihn gleichzeitig ein Aufruf der Methoden overflow MOVED TO... sync() muß beim ungepufferten Puffer nicht überladen werden
    • das Non Virtual Interface ( NVI ) Prinzip gibt und daher unsere Schnittstellen vor

     

    MemberfunktioinBedeutung
    virtual int_type overflow(int_type c ) leere den Streampuffer
    virtual int sync() synchronisiere den Puffer mit dem externen Device

     

    • ausgehend von TeeStream will ich nun einen filterndes Ausgabestreampuffer Framework entwickeln
      • Filter: zwischen den ursprünglichen Streampuffer und das externe Device lassen sich neue Streampuffer als Filter dazwischen schalten
      • Ausgabestreampuffer: der Ansatz zielt nur auf den Ausgabestreampuffer
      • Framework: die Architektur liefert eine fest definierte Schnittstelle, an die man seine Erweiterungen anbringen kann
    Ohne Puffer:
    • alle Zeichen, die an den Streampuffer geschickt werden, sollen im ersten Ansatz direkt an das externe Device weitergeleitet werden
    • die drei Komponenten, aus denen sich das Framework zusammensetzt sind.
      1. der Outputstream, der den Rahmen für die erweiterte Funktionalität bietet
      2. der Streampuffer, der die Aufrufe an den Filter weiterleitet
      3. der Filter bzw. Insertor, der die erweitertete Funktionalität zur Verfügung stellt
    • aufgrund der Übersichtlichkeit habe ich alles in den Headern implementiert
    Outputstream
    #ifndef FILTERING_OUTSTREAM_H
    #define FILTERING_OUTSTREAM_H

    #include "Insertor.h"
    #include "FilteringOutputStreambuf.h"

    class FilteringOutstream
    : private FilteringOutputStreambuf , public std::ostream{

    public:

    FilteringOutstream( std::streambuf* des, Insertor* insert ): FilteringOutputStreambuf( des , insert ), std::ostream(this){}

    virtual ~FilteringOutstream(){};

    };
    #endif // FILTERING_OUTSTREAM_H

     

    • FilteringOutputStream erbt die Implementierung von FilteringOutputStreambuf und das Interace von std::ostream
    • durch das Ableiten von FilteringOutputStreambuf wird erreicht, daß öffentlichen Methoden von ihm zur Ausgabe der Zeichen verwendet werden können
    FilteringOutputStreambuf

    Durch das Überschreiben der Methode overflow()können wir das Verhalten Streampuffers an unsere Bedürfnisse anpassen.

    #ifndef FILTERING_OUTPUT_STREAMBUF_H
    #define FILTERING_OUTPUT_STREAMBUF_H

    #include <iostream>

    #include "Insertor.h"
    class FilteringOutputStreambuf: public std::streambuf {


    public:

    typedef std::char_traits<char> traits_type;
    typedef std::char_traits<char>::char_type char_type;

    typedef traits_type::int_type int_type;

    FilteringOutputStreambuf(std::streambuf* extBuf , Insertor* insert_ ): extBuffer_( extBuf ), insertor_( *insert_ ){};


    public:

    virtual int_type overflow( int_type c = traits_type::eof() ){


    if( !traits_type::eq_int_type( c, traits_type::eof()) ){

    if ( insertor_( *extBuffer_ , traits_type::to_char_type(c) ) < 0 ) return traits_type::eof();

    else return c;
    }
    return traits_type::not_eof(c);

    }


    private:

    std::streambuf* extBuffer_;
    Insertor& insertor_;

    };

    #endif // FILTERING_OUTPUT_STREAMBUF_H

     

    • die drei typedefs am Anfang der Klasse führen die entsprechenden Zeichentypen abhängig vom std::char_traitein
      • nur zur Methode overflow
        • ein Rückgabetype der Form traits_type::eof() wird als failbit vom aufrufenden Stream interpretiert MOVED TO... der Stream ist im bad state und blockiert daher jede Ausgabe
        • das Defaultargument traits_type::eof() erlaubt den Aufruf der Methode ohne Argument, um das explizite flushen zu gewährleisten
        • mittels !(traits_type::eq_int_type( c, traits_type::eof() ) wird geprüft, ob das zu verarbeitende Zeichen vom Typ eof ist; da die Rückgabe von traits_type::eof() ein "reguläres" Zeichen, der Wert traits_type::eof() aber als failbit vom aufrufenden Stream interpretiert wird, muss das Zeichen nach traits_type::not_eof(c) gecastet werden
        • die eigentliche Funktionalität des Streampuffers ist in dem Funktoraufruf Insertor_(*extBuffer_ , traits_type::to_char_type(c)) implementiert, der bei einem Fehler traits_type::eof() zurückgibt
        • da der Streampufferfilter nur als Wrapper um den nativen Streampuffer versteht, wird das Zeichen im ergänzenden else an den gewrappten Puffer weitergegeben
    Filter

    Da der insertor_einen Zustand benötigt, bietet es sich an, diesen als Functor zu implementieren:

    #include <streambuf>

    class Insertor{

    public:

    typedef std::char_traits<char>::char_type char_type;

    typedef std::char_traits<char>::int_type int_type;
    typedef std::char_traits<char> traits;


    int_type operator() (std::streambuf& dst, char_type ch ){


    return processChar( dst, ch );

    }

    private:


    virtual int_type processChar( std::streambuf& dst, char_type ch ){


    return dst.sputc( ch );

    }

     

    • die Klasse folgt dem NVI - Prinzip um das Interface stabil zu halten und Variation auf der virtuellen Methode processChar anzubieten
    • durch die Methode int_type operator() (std::streambuf& dst, char_type ch) wird die Klasse Insertor zum Funktor
    • die Funktionalität der Charakterverarbeitung sollte in processChar implementiert werden, denn die Defaultimplementierung reicht das Argument einfach an den originären Streampuffer mittels dessen öffentlichen Methode sputc durch
    • ein paar Beispiele sollten deren Einsatz aufzeigen
    • ALERT! der bisherige Ansatz besitzt aber noch deutliche Einschränkungen:
    1. keine Strings können bearbeitet werden MOVED TO... puffern der Daten
    2. kein sauberes Interface zum Schreiben auf den originären Streampuffer MOVED TO... Kapslung durch ein internen Puffer
    3. die Funktoren können nicht verkettet werden MOVED TO... Einführung eines Joininsertor
    4. schlechte Perfomande durch das prozessieren auf einzelnen Zeichen
    Mit Puffer

    Der klassische Ansatz für den Puffer:

    • definiere ein char Array:
    static const int bufSize= 16;

    char_type buffer[bufSize];

     

    • initialisiere den Puffer mit deinem Array im Konstruktor:
    setp( buffer , buffer + bufSize ); // init the pufferposition

     

    • falls Zeichen auf den Puffer geschrieben werden, passe den Pufferzeiger an
    // write newChars to the streambuffer; beginning with the position pptr()
    memcpy( pptr(), newChars, numberOfNewChars * sizeof( char_type ) );

    pbump( numberOfNewChars ); // adjust the next pointer of the buffer

     

    • wird der Puffer geleert, passe den Pufferzeiger an
    pbump( - numberOfFlushedChars ); // adjust the nest pointer accoding to the flushed characters

     

    So könnten demnach eine Implementierung aussehen, wobei in der Methode overflow()ein Fehler ist.

    /* The following code example is taken from the book
    * "The C++ Standard Library - A Tutorial and Reference"
    * by Nicolai M. Josuttis, Addison-Wesley, 1999
    *
    * (C) Copyright Nicolai M. Josuttis 1999.

    * Permission to copy, use, modify, sell and distribute this software
    * is granted provided this copyright notice appears in all copies.
    * This software is provided "as is" without express or implied
    * warranty, and with no claim as to its suitability for any purpose.
    */
    #include <cstdio>

    #include <streambuf>

    // for write():
    #ifdef _MSC_VER
    # include <io.h>
    #else
    # include <unistd.h>

    #endif
    class outbuf : public std::streambuf {
    protected:

    static const int bufferSize = 10; // size of data buffer
    char buffer[bufferSize]; // data buffer

    public:
    /* constructor
    * - initialize data buffer
    * - one character less to let the bufferSizeth character
    * cause a call of overflow()

    */
    outbuf() {
    setp(buffer, buffer+(bufferSize-1));

    }

    /* destructor
    * - flush data buffer
    */
    virtual ~outbuf() {

    sync();
    }

    protected:
    // flush the characters in the buffer
    int flushBuffer() {

    int num = pptr()-pbase();
    if (write(1, buffer, num) != num) {

    return EOF;
    }
    pbump(-num); // reset put pointer accordingly

    return num;
    }

    /* buffer full
    * - write c and all previous characters
    */

    virtual int_type overflow(int_type c) {
    if (c != EOF) {

    // insert character into the buffer
    *pptr() = c;
    pbump(1);

    }
    // flush the buffer
    if (flushBuffer() == EOF) {

    // ERROR
    return EOF;
    }
    return c;

    }

    /* synchronize data with file/destination
    * - flush the data in the buffer
    */
    virtual int sync() {

    if (flushBuffer() == EOF) {
    // ERROR

    return -1;
    }
    return 0;
    }

    };

     

    • TIP in overflow() wird müsste man noch prüfen, ob er Puffer voll ist (pptr() == epptr()), bevor man in flushed
    • dieses "klassische" Interface hat meines Erachtens ein paar Nachteile
      1. aufwändiges Interface
      2. explizites Berechnen und Setzten der Position des next Zeigers ist nötig
      3. Puffergröße ist eine statische Größe MOVED TO... C Interface
      4. Puffer wächst nicht dynamisch MOVED TO... C++ Interface
    • MOVED TO... ich werde als Puffer einen std::string verwenden und diesen zum Member von Insertor erklären, denn dort findet das ganze Filtern statt
    • ein weiterer Vorteil des std::string besteht darin, daß dieser neben den STL Algorithmen ein eigenes Interface bietet um ihn zu bearbeiten
    OutputStream
    • das Framework besteht wieder aus den drei Komponenten Outputstream, Streampuffer und Insertor
    • die Implementierung des OutputStream im gepufferten Streampuffer unterscheidet sich nur in zwei Punkten von der vorherigen Version
      1. der zu filternde Streampuffer wird direkt durch den Insertor initialisiert
      2. die Ressource Insertor wird - der Einfachheit halber - durch einen auto_ptr verwaltet
        • alle anderen Ansätze bergen durch die Abhängigkeiten vom Insertor und seinem Stringpuffer deutliche Nachteile bzw. Probleme, denn der Insertor muß dynamisch allokiert werden:
          1. Puffer dynamisch allokieren und dem Insertor zuweisen MOVED TO... aufwändig und Gefahr eines memory leaks
          2. Puffer statisch allokieren und dem Insertor zuweisen MOVED TO... aufwändig
          3. Puffer als Member von Insertor MOVED TO... sehr gefährlich, denn durch das Löschen des Insertor ist das anschließenden flushen des Puffer als Member des Insertor natürlich undefiniert
    #ifndef FILTERING_OUTSTREAM_H
    #define FILTERING_OUTSTREAM_H


    #include "Insertor.h"
    #include "FilteringOutputStreambuf.h"

    #include <memory>
    #include <iosfwd>

    class FilteringOutstream
    : private FilteringOutputStreambuf , public std::ostream{

    public:

    FilteringOutstream( std::auto_ptr <Insertor > insert ): FilteringOutputStreambuf( insert ), std::ostream(this){}

    virtual ~FilteringOutstream(){};

    };
    #endif // FILTERING_OUTSTREAM_H

     

    FilteringOutputStreambuf

    Neben der virtuellen Mehthode overflow() muss jetzt noch die virtuelle Methode sync()implementiert werden, um die Synchronisation zwischen Streampuffer und externen Device zu gewährleisten.

    #ifndef FILTERING_OUTPUT_STREAMBUF_H

    #define FILTERING_OUTPUT_STREAMBUF_H

    #include <iostream>
    #include <memory>

    #include "Insertor.h"
    class FilteringOutputStreambuf: public std::streambuf {



    public:

    typedef std::char_traits<char> traits_type;
    typedef std::char_traits<char>::char_type char_type;

    typedef traits_type::int_type int_type;

    FilteringOutputStreambuf( std::auto_ptr <Insertor > insert_ ): insertor_( insert_){};

    virtual ~FilteringOutputStreambuf(){

    sync();

    }

    protected:

    virtual int_type overflow( int_type c = traits_type::eof() ){


    if( !traits_type::eq_int_type( c, traits_type::eof()) ){

    if ( (*insertor_)( traits_type::to_char_type(c) ) < 0 ) return traits_type::eof();

    else return c;
    }
    return traits_type::not_eof(c);

    }

    virtual int_type sync(){

    return ( insertor_->flushBuffer() == traits_type::eof() )? -1 : 0 ;


    }

    private:

    std::auto_ptr < Insertor > insertor_;

    };

    #endif // FILTERING_OUTPUT_STREAMBUF_H

     

    • der wesentliche Unterschiede zum oben definierten FilteringOutputStreambuf besteht darin, daß dieser die Methode sync() besitzt, um sich mit dem externen Device zu synchronisieren
    • diese Synchronisation kann explizit durch ein flushen des Streams erzwungen werden oder implizit beim zerstören des Streamspuffers eintreten
    • falls das Synchronisieren erfolgreich war, sollte ein Wert von 0 andererseits ein Wert von -1 zurückgegeben werden
    • da sowohl der Puffer der Streampuffer als auch der zu filternde Streampuffer zur Initialisierung der Insertor verwendet werden, genügt es dem insertor_ als einziges Argument das zu prozessierende Zeichen mitzugeben
    Insertor
    #ifndef INSERTOR_H

    #define INSERTOR_H

    #include <cctype>
    #include <memory>
    #include <string>
    #include <streambuf>

    #include <iostream>
    #include <iterator>

    class Insertor{

    public:

    typedef std::char_traits<char>::char_type char_type;
    typedef std::char_traits<char>::int_type int_type;

    typedef std::char_traits<char> traits;

    Insertor(){}

    virtual ~Insertor(){}

    Insertor( std::streambuf* origBuffer ): dst( origBuffer ){


    bufferOut_ = new std::string;
    bufferIn_ = new std::string;

    }

    friend class JoinInsertor;


    int_type operator() ( char_type ch ){

    return appendToInBuffer( std::string(1,ch) ) ;


    }

    int_type flushBuffer(){

    *bufferOut_ = getFilteredBuffer( *bufferIn_ );

    int leng= bufferOut_->length();

    dst->sputn( bufferOut_->data() , leng);

    *bufferIn_= "";
    *bufferOut_= "";

    return ( leng != 0 )? leng : traits::eof();

    }

    protected:

    int_type appendToInBuffer( std::string subStr ){

    *bufferIn_ += subStr;

    return subStr.length();

    }

    private:


    virtual std::string getFilteredBuffer( std::string str ) { return str; }

    static std::string* bufferIn_;
    static std::string* bufferOut_;

    std::streambuf* dst;

    };

     

    • bufferIn_ wird mittels der Zeichen des zu filternden Streams gefüllt
    • bufferOut_ hingegen enhält die modifizierten Zeichen, die zum externen Device geschickt werden
    • durch die Trennung der originären von den gefilterten Daten ist es möglich, ein komplexeren Zugriff auf die gepufferten Daten anzubieten; um dies Interface müsste aber der gefilterte Stream noch erweitert werden MOVED TO... zurückspielen der gefilterten Daten, sofern sie noch nicht geflusht wurden
    • flushBuffer:
      • diese Methode wird durch das explizite std::endl,sync() oder das implizite delete,out of scope synchronisieren des Puffers mit seinem extern Device aufgerufen
      • die virtuelle Methode getFilteredBuffer( .. ) produziert den gefilterten Charakterstream aus dem nativen Charakterstream
      • somit ist getFilteredBuffer(...) die einzige Methode, die durch Überladung in dem Framework angepaßt werden muß
      • mittels dst->sputn(bufferOut_->data() ,leng); werden leng Daten aus bufferOut->data() auf den zu filternden Streampuffer - das externe Device - geschoben
    JoinInsertor
    • dieser erlaubt es beliebig lange Insertorketten zu bilden
    • seine virtuelle Methode getFilteredBuffer(...) bildet getFilteredBuffer(...) nur auf seine zwei gewrappten Insertoren ab:
    secondInsertor_->getFilteredBuffer( firstInsertor_->getFilteredBuffer( str )  )

     

    • JoinInsertor muß zum friend erklärt werden, da er die private Methode getFilteredBuffer(...) ansprechen muß
    • mittels JoinInsertor( Ins1, Ins2 ) wird dieser initalisiert
    • um das Leben leiter zu machen, bietet es sich an, jedem Insertor ein Konstruktor ohne Streampuffer zur Verfügung zu stellen, da diese beiden Puffer bei der Insertorverkettung überflüssig sind
    • erst der finale Insertor, der an den Stream gebunden wird, benötigt natürlich die beiden Puffer um Daten auf einen Streampuffer zu schreiben
    Insertor* in0  = new ReplaceStringInsertor( strMap0); 
    Insertor* in1 = new ReplaceStringInsertor( strMap1);


    Insertor* final = new JoinInsertor( outStream.rdbuf() , in0, in1 );

    FilteringOutstream joinOut( final );

     

    Verbesserungen

    Trennung des Codes in Header- und Sourcedatei.

    • Framework unabhängig vom Charaktertyp char implementieren
    • Iteratoren verbessern
      • Filter, die selbst Funktoren annehmen (vgl. STL)
      • JoinInsertor einfacher zu initialisieren

    Eingabepuffer

    • Die zwei Funktionen, um Zeichen vom internen Puffer zu erhalten, sind sgetc() und sbumpc() .
    • Existiert der interne Puffer beim initialen Aufruf noch nicht oder ist er leer, so sorgen die virtuellen und nicht öffentlichen Methoden underflow() oder uflow()dafür, daß die Puffer gefüllt werden.
      • sgetc() ruft die Methode underflow() auf, da durch sie das Zeichen nur dargestellt, aber nicht konsumiert wird
      • sbumpc() hingegen ruft implizit die Methode uflow() auf, da durch sie das Zeichen vom konsummiert wird.
    • Der Streampufferfunktionalität sichert zu, daß man mindestens ein Zeichen zum internen Puffer zurückzuschreiben kann.
      • sputbackc(c) schreibt das Zeichen c zurück; dies muß nicht das zuletzt gelesen Zeichen sein
      • sungetc() dekrementiert nur den Pufferzeiger.
      • Während sputback(c) beim nächsten Leseaufruf c zurückgibt, wird durch sungetc() das letzte Zeichen nochmals dargestellt.
      • die virtuelle, nicht öffentliche Methode pbackfail sorgt dafür , das die Zeigerpositionen gemäss sputback(c) und sungetc() richtig gesetzt wird.
      • Darüber hinaus stellt pbackfail eine internen Puffer bereit.
    • Zusammenfassend nochmals die drei virtuellen, nichtöffentlichen Methoden, die das Framework Streampuffer zur Verfügung stellt um es zu konfigurieren.

      MethodeBedeutungReturnwertAufrufer
      uflow() stellt ein Zeichen vom internen Puffer oder externen Device zur Verfügung Zeichen sbumpc()
      underflow() stellt ein Zeichen vom internen Puffer oder externen Device zur Verfügung Zeichen sgetc()
      pbackfail() stellt das letzte Zeichen der internen Puffers zur Verfügung Zeichen sputback() oder sungetc()
    • bei ihrer Verwendung müssen ein paar Dinge beachtet werden
      • uflow() wird durch underflow() implementiert
      • beim ungepufferten Einlesen müssen beide Methoden separat definiert werden um ihrer verschiedenen Semantik zu genügen
      • beim gepufferten Einlesen ist es ausreichend underflow() zu implementieren, da die Pufferpositionierng hier schon vom internen Puffer gehandelt wird
      • pbackfail() muß nur beim umgepuffeten Einlesen implementiert werden, da mindestens ein Zeichen laut Standard gepuffert werden muß
    klasssische Vorgehensweise
    ungepufferten Streampuffer
    • definierte die Methoden underflow(), uflow und pbackfail()
    • lege eine Zeichenpuffer der Länge 1 an
    • fülle diesen Zeichenpuffer mit einem Zeichen des externen Devices, falls underflow() oder uflow() aufgerufen wird und gib dieses Zeichen zurück
    • gib dies Zeichen bei jedem weiteren Aufruf von underflow() oder pbackfail()zurück
      • falls uflow() prozessiert wird, lies ein neues Zeichen vom externen Device ein, setze den Zeichenpuffer neu damit und gib dies Zeichen zurück
      • charBuf soll nun der Zeichenpuffer sein und takeFromBuf eine bool Variable
    int_type unbuffered_streambuf::underflow(){

    if ( takeFromBuf ) return traits_type:to_int_type(charBuf);


    else{

    char_type c;
    if (char_from_device(c) <0 ) return traits_type::eof();

    else{
    takeFromBuf= true;
    charBuf= c;

    return traits_type::to_int_type(c);

    }
    }


    }

    int_type unbuffered_streambuf::uflow(){

    if ( takeFromBuf ){


    takeFromBuf= false;
    return traits_type:to_int_type(charBuf);

    }
    else{

    char_type c;
    if (char_from_device(c) <0 ) return traits_type::eof();

    else{
    charBuf= c;
    return traits_type::to_int_type(c);

    }
    }
    }

    int_type unbuffered_streambuf::pbackfail(){

    if ( !takeFromBuf ){

    if ( !traits_type::eq_int_type(c,traits_type::eof() ) char_buf= traits_type::to_char_type(c);

    takeFromBuf= true;
    return traits_type::to_int_type( charBuf );

    }
    else return traits_type::eof();

    }

     

    gepufferten Streampuffer
    • definiere underflow()
    • lege ein Zeichen Array an der Länge bufferSize und der put back Länge pbSize an
    static const int bufferSize= 10;
    static const int pbSize= 4;

    char buffer[bufferSize];

     

    • initialisiere den internen Puffer:
    setg( buffer + pbSize, buffer + pbSize, buffer + pbSize );

     

    • durch das erste Nutzen des Inputstreambuffers wird die Methode underflow() aufgerufen, was zum Lesen von num Zeichen vom externen Device führt
    • es werden numPutback Zeichen auf den put backBereich des internen Puffers geschrieben
      • letztendlich müssen die Zeiger auf den internen Puffer angepasst werden
    setg( buffer + ( pbSize - numPutback) ,

    buffer + pbSize ,
    buffer + pbSize + num );

     

    • MOVED TO...nun können bis zu numPutback Zeichen wieder zurück auf den internen Puffer geschoben werden
      • andernfalls gibt man ein Zeichen an den internen Puffer zurück, das schon an den Stream weitergeleitet wurde
    /* The following code example is taken from the book

    * "The C++ Standard Library - A Tutorial and Reference"
    * by Nicolai M. Josuttis, Addison-Wesley, 1999
    *
    * (C) Copyright Nicolai M. Josuttis 1999.
    * Permission to copy, use, modify, sell and distribute this software
    * is granted provided this copyright notice appears in all copies.

    * This software is provided "as is" without express or implied
    * warranty, and with no claim as to its suitability for any purpose.
    */
    #include <cstdio>
    #include <cstring>

    #include <streambuf>

    // for read():
    #ifdef _MSC_VER
    # include <io.h>
    #else
    # include <unistd.h>

    #endif

    class inbuf : public std::streambuf {
    protected:

    /* data buffer:
    * - at most, four characters in putback area plus
    * - at most, six characters in ordinary read buffer
    */
    static const int bufferSize = 10; // size of the data buffer

    char buffer[bufferSize]; // data buffer

    public:
    /* constructor

    * - initialize empty data buffer
    * - no putback area
    * => force underflow()
    */
    inbuf() {

    setg(buffer+4, // beginning of putback area
    buffer+4, // read position

    buffer+4); // end position
    }

    protected:
    // insert new characters into the buffer

    virtual int_type underflow() {

    // is read position before end of buffer?
    if (gptr() < egptr()) {

    return traits_type::to_int_type(*gptr());
    }

    /* process size of putback area

    * - use number of characters read
    * - but at most four
    */
    int numPutback;
    numPutback = gptr() - eback();

    if (numPutback > 4) {
    numPutback = 4;

    }

    /* copy up to four characters previously read into
    * the putback buffer (area of first four characters)
    */
    std::memmove(buffer+(4-numPutback), gptr()-numPutback,

    numPutback);

    // read new characters
    int num;
    num = read(0, buffer+4, bufferSize-4);

    if (num <= 0) {
    // ERROR or EOF
    return EOF;

    }

    // reset buffer pointers
    setg(buffer+(4-numPutback), // beginning of putback area

    buffer+4, // read position
    buffer+4+num); // end of buffer

    // return next character
    return traits_type::to_int_type(*gptr());
    }

    };

     

  •  

      Linux-Magazin
    TitelInhaltDatum
    Deko mit Nutzen Dekoratoren in Python 06/2009
    Im Zeichen der Drei Umstieg auf Python 3 09/2009
    Erfrischend neu C++0x, Teil 1: Die Erweiterung der Kernfunktionalität 04/2010
    Reichhaltiges Angebot C++0x, Teil 2: Die Erweiterung der Standardbibliothek 05/2010
    Magischer Mechanismus Template Metaprogramming mit C++ 01/2011
    Kurz und bündig Prägnante Programmierung in Haskell 06/2011
    Passendes Werkzeug C++ Compiler im Vergleich 12/2017
    Unterschiedlich Quicksort Implementierunge im Vergleich Quicksort 03/2021

     

      Linux-Magazin Online
    TitelInhaltDatum
    Funktionale Programmierung (1) Grundzüge der funktionalen Programmierung 09/2009
    Funktionale Programmierung (2) Funktionale Programmierung mit Python 10/2009
    Funktionale Programmierung (3) Das MapReduce Framework 11/2009

     

      Linux-Magazin Pro
    TitelInhaltDatum
    What’s new in Python 3 What do Python 2.0 programmers need to know about Python 3? 107/2009
    GCC, Clang, and MSVC compilers with C++ Perfect Match 207/2018
  • Rezensionen

      Linux-Magazin
    TitelAutorDatum
    Das Python Praxisbuch Farid Hajji 10/2009
    The C++ Standard Library Extension Pete Becker 02/2010
    Patterns For Parallel Software Design Jorge Luis Ortega-Arjona 07/2010
    Clojure Stefan Kamphausen, Tim Oliver Kaiser 01/2011
    The D Programming Language Andrei Alexandrescu 03/2011
    Haskell Intensivkurs Marco Block, Adrian Neumann 06/2011
    Learn You a Haskell for Great Good! Miran Lipovaca 06/2011
    Effektiv C++ programmieren Scott Meyers 10/2011
    Haskell: The Craft of Functional Programming Simon Thompson 02/2012
    Hadoop: Zuverlässige, verteilte und skalierbare Big-Data-Anwendungen Ramon Wartala 04/2012
    Multicore-Software Urs Gleim, Tobias Schüle 06/2012
    The C++ Standard Library Nicolai Josuttis 08/2012
    Template Metaprogramming Davide di Gennaro 11/2012
    Erlang/OTP Pavlo Baron 04/2013
    Scala in Depth Joshua Suereth 05/2013
    MapReduce Design Pattern Donald Miner, Adam Shook 06/2013
    Introducing Erlang Simon St. Laurent 06/2013
    The C++ Programming Language Bjarne Stroustrup 11/2013
    DSL Engineering Markus Völter 03/2014
    Requirements Engineering Ralf Baumann 08/2014
    Parallel und Concurrent Programming in Haskell Simon Marlow 09/2014
    From Mathematics to Generic Programming Alexander A. Stepanov 06/2015
    Discovering Modern C++ Peter Gottschling 05/2016
    Clean C++ Stephan Roth 03/2018
  •  

     
       
    TitelInhaltSourcecodeDatum
    Die Elf spielt auf Lambda-Funktionen I Listing 1
    Listing 2
    Listing 3
    12/2011
    Kurz und knackig Lambda-Funktionen II Listing 1
    Listing 2
    Listing 3
    Listing 4
    Listing 5
    02/2012
    Mehrgleisig unterwegs Multithreading I dotProduct.cpp
    dotProductPara.cpp
    04/2012
    Gemeinsam ins Ziel Multithreading II join.cpp
    forgetJoin.cpp
    bossWorker.cpp
    bossWorkerMutex.cpp
    bossWorkerLockGuard.cpp
    bossWorkerUniqueLock.cpp
    06/2012
    Im Gleichtakt Multithreading III boss2WorkerCondVar.cpp
    boss2WorkerLock.cpp
    bossWorker.cpp
    bossWorkerBlock.cpp
    08/2012
    Alle im Einklang Multithreading IV bossWorkerPrototype.cpp
    bossWorkerFutures.cpp
    10/2012
    Rasch verschoben Rvalue Referenzen bigArray.cpp
    bigArrayCopy.cpp
    bigArrayTime.cpp
    bigArrayCopyTime.cpp
    perfectForwarding.cpp
    12/2012
    Räumkommando unique_ptr autoPtr.cpp
    resourceGuard.cpp
    uniquePtr.cpp
    02/2013
    Klug aufgeräumt shared_ptr Listing 1
    Listing 2
    Listing 3
    Listing 4
    04/2013
    Zähl mich! Reguläre Ausdrücke I Listing 06/2013
    Starke Ausdrücke Reguläre Ausdrücke II allEmails.cpp
    match.cpp
    search.cpp
    08/2013
    Suchen und ersetzen Reguläre Ausdrücke III anonymizeIPs.cpp
    anonymizePartiallyIPs.cpp
    formateDate.cpp
    replaceDoubles.cpp
    10/2013
    Geschwindigkeit zählt Hashtabellen mapHash.cpp
    mapHashCompare.cpp
    12/2013
    Neue Ausdruckskraft auto, decltype und die Range-basierte For-Schleife auto.cpp
    out.cpp
    out3.cpp
    rangeBasedForLoop.cpp
    02/2014
    C++11 + 3 = C++14 C++14 automaticReturn.cpp
    lambda.cpp
    literal.cpp
    readerWriter.cpp
    04/2014
    Der Vertrag Das Memory-Modell syncAtomic.cpp
    syncAtomicAcquireRelease.cpp
    syncAtomicRelaxed.cpp
    syncMutex.cpp
    withoutSync.cpp
    06/2014
    Automatik mit Methode default, delete, override und final default.cpp
    defaultDelete.cpp
    final.cpp
    virtualFunctionsOverride.cpp
    08/2014
    Schönes Objekt

    Direkte Initialisierung

    Sequenzkonstruktor, Delegation und Vererbung von Konstruktoren

    sequenceConstructor.cpp
    directInitialization.cpp
    delegationConstructor.cpp
    inheritingConstructor.cpp
    10/2014
    Kurs zum Mars

    Raw-String- und benutzerdefinierte Literale

    Raw-String-Literale
    Benutzerdefinierte Literale

    12/2014
    Für vorsichtige Raser

    Type-Traits

    Listing 1
    Listing 2
    Listing 3
    Listing 4

    04/2015
    Konstante Magie

    constexpr

    Listing 1
    Listing 2
    Listing 3

    06/2015
    Punktlandung

    Variadic Templates...

    Listing 1
    Listing 2
    Listing 3
    Listing 4

    08/2015
    Containerverwaltung

    Neue Algorithmen für Container

    allAnyNoneOfMap.cpp
    allAnyNoneOfVec.cpp
    emplace.cpp
    shrinkToFit.cpp

    10/2015
    Der Reihe nach verpackt

    std::array, std::tuple und std::forward_list

    Listing 1
    Listing 2
    Listing 3
    Listing 4

    12/2015
    Doppelte Packung Referenz-Wrapper

    Listing 1
    Listing 2
    Listing 3

    02/2016
    Die Zeit verstehen Die Zeit Bibliothek I

    Listing 1
    Listing 2
    Listing 3
    Listing 4

    04/2016
    Pünktlich verschlafen Die Zeit Bibliothek II

    Listing 1

    06/2016
    Zukunftsmusik C++17 I

    Listing 1
    Listing 2
    Listing 3

    08/2016
    Bibliotheks-Karriere C++17 II

    Listing 1
    Listing 2
    Listing 3
    Listing 4
    Listing 5

    10/2016
    Von der Theorie zu Praxis Wie setzt der Programmierer die Feature von modernem C++ richtig ein?   12/2016
    Deliquente Typen Vermeide implizite Typkonvertierungen

    Listing 1
    Listing 2
    Listing 3
    Listing 4
    Listing 5
    Listing 6
    Listing 7
    Listing 8
    Listing 9

    02/2017
    Programmiere deklarativ  Programmiere deklarativ Listing 1
    Listing 2
    Listing 3
    Listing 4
    Listing 5
    04/2017
    Selbstoptimiert Unterstütze automatische Optimierung Listing 1
    Listing 2
    Listing 3
    06/2017
    Compiler first Sei nicht schlauer als der Compiler Listing 1
    Listing 2
    Listing 3
    08/2017
    Crash nach Flash Behalte das große Bild im Auge

    Listing 1
    Listing 2
    Listing 3
    Listing 4
    Listing 5
    Listing 6

    10/2017
    Kein Verlass Vermeide undefiniertes Verhalten Sourcecode 12/2017
    Schöne Lektüre Achte auf die Lesbarkeit des Codes Sourcecode 02/2018
    Von allen guten Geistern Lasse dir helfen Sourcecode 04/2018
    Schicke Bibliotheken Kenne deine Bibliotheken Sourcecode 06/2018
    Strebe nach Einfachheit Strebe nach Einfachheit Soucecode 08/2018
    Richtlinien-Kompetenz MISRA C++, Autosar C++14 und die C++ Core Guidelines   10/2018
    Goldene Erkenntniss Die C++ Core Guidelines Sourcecode 12/2018
    Gut verträglich Interfaces Sourcecode 02/2019
    Das gewisse Extra Die Guidelines Support Library (GSL) Sourcecode 04/2019
    In Portionen Funktionen Sourcecode 06/2019
    Fein abgeschmeckt Konstruktoren und Destruktoren Sourcecode 08/2019
    Klassisches Rezept Klassenhierachien Sourcecode 10/2019
    Falsche Zutaten Polymorphe Objekte Sourcecode 12/2019
    Auf Sparflamme Ressourcenverwaltung Sourcecode 02/2020
    Pointer-Rezepte Smart Pointer Sourcecode 04/2020
    Rezept für Namen Aussagekräftige Namen Sourcecode 06/2020
    Typische Fallen vermeiden Expressions und Statements Sourcecode 08/2020
    Richtig optimieren Performanz Sourcecode 10/2020
    Performance by Design Performanz Sourcecode 12/2020
    A Day at the Races Concurrency Sourcecode 02/2021
    Verletzungsrisiko Concurrency Sourcecode 04/2021
    Wenn es kracht Fehlerbehandlung Sourcecode 06/2021
    Unveränderlich Konstantheit und Unveränderlichkeit Sourcecode 08/2021
    Mit Schablone Templates Sourcecode 10/2021
    Rückschau 10 Jahre "Modernes C++ in der Praxis" Sourcecode 12/2021
    Schnittstellen Interfaces Sourcecode 02/2022
    Vergleichsweise Anpassung von Algorithmen Sourcecode 04/2022
    Container-Flitzer Sequenzielle Container Sourcecode 06/2022
    Nummernsucher Assoziative Container   08/2022
    Grenzfälle Zugriffe auf Elemente außerhalb eines Containers   01/2023
    The Famous Four Überblick zu Concepts, Ranges, Modulen und Coroutinen   04/2023
    Die verschiedenen Arten, Concepts zu verwenden Einsatz von Concpets   06/2023
    Die Definition von Concepts Benutzerdefinierte Concepts   08/2023
  • Öffentliche Vorträge

     
    JahrTitelVeranstaltungOrtDatumLängeMaterial
    2012 C++11: Quo vadis? CeBIT Hannover 10.03.2012 40 min video
      C++11: Quo vadis? Gesellschaft für Informatik Heidelberg 19.09.2012 90 min pdf
      C++11: Quo vadis? Gesellschaft für Informatik Karlsruhe 17.10.2012 90 min pdf
      C++11: Quo vadis? Gesellschaft für Informatik Dortmund 05.11.2012 90 min pdf
      C++11: An overview Meeting C++ Neuss 09.11.2012 90 min pdf video
      Functional Programming in C++11 Meetting C++ Neuss 10.11.2012 45 min pdf video
      C++11: Quo vadis? Gesellschaft für Informatik Ostwestfalen 04.12.2012 90 min pdf
    2013 Python, die Sprache für den Systemadministrator? CeBIT Hannover 05.03.2013 30 min  
      Embedded programming with C++11 Meeting C++ Düsseldorf 09.11.2013 60 min pdf
    2014 Embedded Programmierung in C++ Advanced Developers Konferenz Garching 29.04.2014 80 min pdf
      Funktionale Programmierung in C++ Advanced Developers Konferenz Garching 30.04.2014 80 min pdf
      Functional Programming in C++ C++ User Group Russia Saratov 24.10.2014 60 min pdf video
      Embedded Programmierung - die Domäne von C++? Embedded Software Engineering Kongress Sindelfingen 02.12.2014 40 min pdf
      Multithreading done right? Meeting C++ Berlin 02.12.2014 60 min pdf video
    2015 Multithreading done right? C++ Conference Moskau 26.02.2015 60 min pdfvideo
      Programmierung zur Compilezeit Advanced Developers Konferenz Erding 06.05.2015 80 min pdf
      Multithreading, richtig gemacht? Advanced Developers Konferenz Erding 06.05.2015 80 min pdf
      Funktionale Programmierung mit C++ Linuxtag Tübingen 13.06.2015 60 min pdf
      Functional Programming in C++ Central-European Functional Programming School Budapest 08.07.2015 90 min pdf
      Programmierung zur Compilezeit Embedded Software Engineering Kongress Sindelfingen 01.12.2015 40 min pdf
    2016 Das C++-Speichermodell Parallel 2016 Heidelberg 06.04.2016 75 min pdf
      15 Tipps (oder warum es nur 10 wurden) Advanced Developers Konferenz Erding 26.04.2016 80 min pdf
      Das C++-Speichermodell Advanced Developers Konferenz Erding 27.04.2016 80 min pdf
      Das C++-Speichermodell C++ Usergruppe München Planegg 28.04.2016 80 min pdf
      15 Tipps (oder warum es nur 10 wurden) Linuxtag Tübingen 11.06.2016 60 min pdf
      15 Tipps (oder warum es nur 10 wurden) oose Abendvortrag Hamburg 29.09.2016 70 min pdf
      The C++ memory model Meeting C++ Berlin 18.11.2016 60 min pdfvideo
      Funktionale Programmierung mit modernem C++ Embedded Software Engineering Kongress Sindelfingen 29.11.2016 45 min pdf
    2017 Funktionale Programmierung in C++ C++ Usergruppe Karlsruhe Karlsruhe 11.01.2017 60 min

    pdf audioTalk audioDiscussion

      Parallelism and Concurrency in C++17 and C++20 Multicore@Siemens Nürnberg 08.02.2017 45 min pdf
      Programming at Compile Time emBO++ Bochum 18.02.2017 45 min pdf
      Programming at Compile Time C++ Conference Moskau 24.02.2017 60 min pdf video
      Funktionale Programmierung in C++ sodge IT GmbH Balingen 13.03.2017 60 min pdf
      Gleichzeitigkeit und Parallelität in C++17 und C++20 Parallel 2017 Heidelberg 30.03.2017 70 min pdf
      Gleichzeitigkeit und Parallelität in C++17 und C++20 C++ Usergruppe München München 03.05.2017 70 min pdf
      Quo vadis Multithreading in C++ Advanced Developers Konferenz München 16.05.2017 70 min pdf
      C++17: Was gibts Neues? Advanced Developers Konferenz München 17.05.2017 70 min pdf
      Threads and Locks must go Meeting C++ Berlin 09.11.2017 60 min pdf video
      Secret Lightning Talk Meeting C++ Berlin 11.11.2017 10 min pdf video
      C++17 Embedded Software Engineering Kongress Sindelfingen 05.12.2017 45 min pdf
    2018 Best Practices für Concurrency Parallel 2018 Heidelberg 07.03.2018 70 min pdf
      Best Practices for Concurrency C++ Russia St. Petersburg 21.04.2018 60 min pdf
      Best Practices für Concurrency C++ Usergruppe Karlsruhe Karlsruhe 10.05.2018 70 min pdf
      Best Practices für Concurrency sodge IT GmbH Balingen 16.05.2018 70 min pdf
      Best Practices für Concurrency Linuxtag Tübingen 09.06.2018 60 min pdf
      Concurrency and Parallelism in C++17 and C++20/23 CoreHard Minsk 03.11.2018 50 min pdf
      The Core Guidelines for Safer Code Meeting Embedded 2018 Berlin 14.11.2018 30 min pdf
      Best Practices for Concurrency Meeting C++ Berlin 17.11.2018 60 min pdf
      Die C++ Core Guidelines für sicheren Code Embedded Software Engineering Kongress Sindelfingen 04.12.2018 40 min pdf
      Migration auf Python 3 Embedded Software Engineering Kongress Sindelfingen 04.12.2018 40 min pdf
    2019 Die bekanntesten (Online-)Compiler im Vergleich Parallel 2019 Heidelberg 21.02.2019 50 min pdf
      Concurreny und Parallelität mit C++17 und C++20/23 Parallel 2019 Heidelberg 21.02.2019 50 min pdf
      Concurrency and Parallelism in C++17 and C+20/23 Cpp Europe Bukarest 26.02.2019 60 min pdf
      Concurrency and Parallelism with C++17 and C++20/23 C++ Russia Moskau 20.04.2019 60 min pdf
      Concepts C++ Italia Mailand 15.06.2019 50 min pdfvideo
      C++20 - Die Revolution geht weiter Linuxtag Tübingen 06.07.2019 50 min pdf
      Concepts CppCon Aurora 16.09.2019 60 min pdfvideo
      Atomics, Locks, and Tasks (Back to Basics) CppCon Aurora 17.09.2019 2 * 60 min

    pdfexamples

    Video1 Video2

      C++20 - The Big Four C++ Russia St. Petersburg 01.11.2019 60 min pdf
      Concepts Meeting C++ Berlin 14.11.2019 60 min pdf video
      Die bekanntesten (Online-)Compiler im Vergleich Embedded Software Engineering Kongress Sindelfingen 03.12.2019 40 min pdf
     2020 Concepts C++ Usergruppe München  Online 26.03.2020 80 min  pdf
      Migration auf Python 3 enterPy Online 26.05.2020 45 min pdf
      Concepts Cpp Europe Online 23.06.2020 60 min pdf 
      Concepts C++ Usergruppe Karlsruhe/Dresden Online 10.07.2020 60 min pdf 
      From Functions to Coroutines CppCon Online 15.09.2020 60 min pdfvideo
      Smart Pointers (Back to Basics) CppCon Online 17.09.2020 60 min pdfvideo
      From Functions to Coroutines Meeting C++ Online 14.11.2020 60 min pdf
      C++20 - Die Revolution geht weiter Embedded Software Engineering Kongress Online 01.12.2020 40 min pdf
    2021 Erweitern und Einbetten von Python enterPy Online 15.04.2021 45 min pdf
      C++20 - Die Revolution geht weiter Advanced Developer Konferenz Online 18.05.2021 60 min pdf
      C++20 - Die Revolution geht weiter Uni Zwickau Online 17.06.2021 80 min pdf
      Concurrency Patterns CppCon Online 25.10.2021 60 min pdf video
      const and constexpr (Back to Basics) CppCon Online 26.10.2021 60 min pdf video
      Object Oriented Programming (Back to Basics) CppCon Online 27.10.2021 60 min pdf video
      C++20: The Small Pearls CppCon Online 28.10.2021 60 min pdf
      C++20: The Hidden Pearls Meeting C++ Online 11.11.2021 60 min pdf video
      Erweitern und Einbetten von Python Embedded Software Engineering Kongress Online 30.11.2021 40 min pdf
    2022 const and constexpr Meeting C++ Online 25.01.2022 60 min pdf video
      Extend and Embed Python Embo++ Online 26.03.2022 50 min pdf
      C++20: The Small Pearls ACCU Bristol 06.04.2022 90 min

    pdfvideo

      Ranges C++20 Techniques for Algorithmic Trading Online 26.04.2022 25 min pdf video (2:32)
      Concurrency Pattern Cpp Europe Online 24.05.2022 60 min pdf
      Extend and Embed Python C++ North Toronto 18.07.2022 60 min pdf video
    2023            
  • Online-Schulungen

     

      Medialinx IT-Academy
    TitelInhaltLängeDatum
    Python für Systemadministratoren Ein kompakter Einstieg in Python für Systemadministratoren 5 Stunden 04/2013

     

      video2brain
    TitelInhaltLängeDatum
    Moderne Software-Entwicklung mit C++11 Ein Überblick über C++11 3 1/2 Stunden 07/2013
    C++ - Das große Training Die aktuelle C++-Kernsprache 10 Stunden 06/2014-08/2014
         
         
         
         
         
  •  

    Operator Überladung

    nach Thinking in C++ Band 1 von Bruce Eckel (Sehr gute Einführung in C++)
    und Large Scale C++ Software Designvon John Lakos (Betrachtungen über Compile- und Linkabhängigkeiten in großen C++ Projekten)

    • Operator Überladung stellt eine zusätzliche Art dar, Funktionsaufrufe zu erzeugen
    • diese zusätzliche Möglichkeit wird gern als "syntatic sugar" bezeichnet
    int main(){                                int main(){
    
      pub_Int a, b, c, d, e , f ;                pub_Int a, b, c, d, e , f ;
    
      a +=1;                                     a.add(1);
    
      c = a+b;                                   c = pub_Int::plus(a,b);
    
      f = a*b*c + b*c*d + c*d*e;                 f= pub_Int::plus(
    
                                                      pub_Int::plus(
                                                        pub_Int::mult(
                                                          pub_Int::mult(a,b),
    
                                                        c),
                                                        pub_Int::mult(
                                                          pub_Int::mult(b,c),
    
                                                        d)
                                                        ),
                                                      pub_Int::mult(
                                                          pub_Int::mult(c,d),
    
                                                      e)
                                                    );
      std::cout << f << std::endl;               pub_Int::print( cout, f ) << std::endl;     
    
    • Lesbarkeit und Codierbarkeit wird durch diese Technik deutlich erhöht, da die Operanden nicht nach dem Operator ( pub_Int::plus (a,b) ) stehen
    • Operator Überladung sollte nur dort angewandt werden, wo es den Code leichter codierbar und insbesondere lesbarer macht

    Einschränkungen

    1. nur in Ausdrücken mit eigenen Datentypen kann man Operator überladen
      right man kann den = << = Operator nicht so überladen, sodass = 1.414 << 2 = vom Compiler angenommen wird
    2. es können keine neuen Operatoren (** für pow) definiert werden, die keine Bedeutung in C besitzen
      • da man hierzu einerseits die Präzedenz der neuen Operatoren definieren müsste und andererseits neue Parsingprobleme (vgl. **) einführt
    3. der Memberfunktionsoperator Operator "." und der Operator ".*" , um Zeiger auf Members zu dereferenzieren, können nicht überladen werden
    struct X {
    
         int X::*pm;
         int y;
    } s;
    
    s.pm = &X::y;
    
    s.pm gives you the pointer to member.
    
    s.*pm // gives you the member pointed at.
    
    1. die Präzedenz der Operatoren kann nicht verändert werden
    2. die Anzahl der Argumente eines Operators steht fest

    Syntax

    • um den Operator @ zu überladen, wird eine Funktion operator@ erklärt
    • die Anzahl der Argumente hängt davon ab, ob es eine unärer oder binärer Operator ist
    • wenn der Operator als Memberfunktion erklärt ist, wird implizit das aufrufende Objekt als Argument übergeben
    //OperatorOverloadingSyntax.cpp
    #include <iostream>
    using namespace std;
    
    class Integer {
    
      int i;
    public:
      Integer(int ii) : i(ii) {}
    
      const Integer
      operator+(const Integer& rv) const {
    
        cout << "operator+" << endl;
        return Integer(i + rv.i);
    
      }
      Integer&
      operator+=(const Integer& rv) {
    
        cout << "operator+=" << endl;
        i += rv.i;
    
        return *this;
      }
    };
    
    int main() {
    
      cout << "built-in types:" << endl;
      int i = 1, j = 2, k = 3;
    
      k += i + j;
      cout << "user-defined types:" << endl;
    
      Integer ii(1), jj(2), kk(3);
    
      kk += ii + jj;
    }
    
    • Vorsicht:
      • const Integer operator+(const Integer& rv) const gibt eine sogenannten "right value" zurück; dieser Typ zeichnet sich dadurch insbesondere aus, dass er keine Adresse hat und so nur auf der rechten Seite von Zuweisungen stehen darf
        right a+b=c+d ist daher nicht möglich
      • hingegen gibt Integer& operator+=(const Integer& rv) eine "left value" zurück, der auf beiden Seiten einer Zuweisung stehen kann

    Unäre Operatoren

    • die Increment- und Dekrementoperatoren sind ein bisschen tricky:
      • für die Inkrementoperator (Decrementoperator) gibt es zwei Formen, die Prefix ++a (--a) und die Postfix a++ (a--) Form
      • die Prefix Form inkrementiert (dekrementiert) erst, bevor sie den Wert zurückgibt; die Postfix Form gibt den Wert unverändert zurück und inkrementiert (dekrementiert) erst danach
      • ++a stößt den operator++(a) und a++ hingen den operator++(a, int) an
      • wenn möglich, sollte der Prefixoperator verwendet werden, da dieser ohne ein temporäres Objekt auskommt
    for (int i =0; i < a; ++i )
    
    for (int i= 0; i < a; i++ )
    

    Binäre Operatoren:

    Ene Sonderstellung geniess der Zuweisungsoperator.

    • der operator= muss als einziger eine Memberfunktion sein
    • bei der Implementierung des Operators sollte man keine Selbstzuweiung zulassen
    Fred& operator= (const Fred& f){
    
      delete p_;                
      p_ = new Wilma(*f.p_);    
      return *this;
    
    }
    
    • falls f und this identisch sind, werden beide Objekte this->p_ und f.p_ durch delete p_ gelöscht, aber in der nächsten Zeile wird das nicht mehr existierende Objekt f.p_ referenziert

    Argument- und Returnwertbetrachtungen nicht nur für Operatoren

    1. Argument: konstante Referenz, falls nur lesend darauf zugegriffen wird
    2. Memberfunktion: const, falls keine Veränderung an dem Objekt (Operanden) geschieht
    3. Returnwert:
      1. "by Value" right ein konstantes, temporäres Objekt wird erzeugt, ein sogenannter "rvalue", der nur zugewiesen werden kann
        die Deklaration von Integer::opertor+ unterbindet a+b=c+d
      2. "by Reference" right durch die Rückgabe als "lvalue" sind Operatorketten wie a=b=c möglich
        falls der Returnwert modifzierbar sein soll, darf die Referenz nicht const sein (a=b).foo()
    4. Logische Operatoren sollten ein bool Objekt zurückliefern
    5. Returnwertoptimierung:
      1. return Integer(left.i + right.i)
        • hier greift die Returnwertoptimierung, sodass nur ein Konstruktoraufruf benötigt wird
        • der Compiler sieht, dass es hier nur um einen Returnwert geht
      2. =Integer tmp(left.i + right.i );  return tmp; =
        • ein Konstrukt-, ein Kopykonstruktor- und ein Destruktoraufruf wird für tmp benötigt

    Member- versus Nichtmember Operator

    OperatorEmpfohlener Gebrauch
    alle unären Operatoren Member
    = () [] -> ->* müssen Member sein
    += -= /= *= ^= &= %= >>= <<= Member
    die restlichen binären Operatoren nicht Member
    • Freie Funktion:
      • unterstützte "user-defined" Konvertierung des am weitestesten Links stehenden Arguments (implizite Argument bei Memberoperatoren)
      • Symmetrie der binären Operatoren ( z.B.:.: == < + )
    • Memberoperator:
      • unterbinde "user-defined" Konvertierung des am weitestesten Links stehenden Arguments
      • Operator modifiziert den Operand ( z.B.: = += *= ++ )
      • C++ Standard schreibt es vor ( z.B.: () [] -> )

    Beispiele

    user-defined Konvertierung oder auch Wieso sollte der += operator Member sein ?

    class pub_String{
      public:
        pub_String( const char* str);
    
        const pub_String operator+ ( const& pub_String right) const;
    
        friend pub_String& operator+=( pub_String& left, const pub_String& right);
    
    }
    
    void f(){
    
      pub_String s("foo"), t("");
    
      int i;
    
      t= s+ "bar";    // ok
    
      t= "bar" + s;   //error
      i= s == "bar";  // ok
    
      i= "bar" == s;  // error
    }
    
    void g(){
    
      pub_String a("tar");
      const char* b= "foo";
    
      pub_String c("bar");
      b += c;            // keine Wirkung auf b;
      a += b += c;       // keine Wirkung auf b, aber auf a
    
    }
    
    • zu f: das Bereitstellen des Konvertierungsoperators von const char* nach pub_String bricht die Symmetrie
    • zu g:
      • b += c; besitzt keine Wirkung, da c hier nicht *b, sondern dem temporären Objekt pub_String("foo") zugewiesen wird
      • a += b += c; verändert nur a, da das temporäre Objekt pub_String("foo") an a gebunden werden kann; b bleibt aber dennoch unverändert

    Symmetrie oder auch Wieso sollte der + operator frei sein ?

    class Number {
      int i;
    
    public:
      Number(int ii = 0) : i(ii) {}
    
      const Number
      operator+(const Number& n) const {
    
        return Number(i + n.i);
      }
      friend const Number
        operator-(const Number&, const Number&);
    
    };
    
    const Number
      operator-(const Number& n1,
                const Number& n2) {
    
        return Number(n1.i - n2.i);
    }
    
    int main() {
      Number a(47), b(11);
    
      a + b; // OK
      a + 1; // 2nd arg converted to Number
    
    //! 1 + a; // Wrong! 1st arg not of type Number
      a - b; // OK
      a - 1; // 2nd arg converted to Number
    
      1 - a; // 1st arg converted to Number
    }
    
    • durch den Konvertierungskonstruktor
      Number(int ii = 0)
      können die Operatoren + und - für int verwendet werden
    • 1 + a kann vom Compiler nicht verwertet werden, da das linke Argument vom Typ Number sein muß

    Automatische Typkonvertierung

    Konstruktor

    • ein typisches Beispiel ist Number(int ii = 0 )
    • unterbinden der impliziten Konstruktorkonvertierung:
      • durch explicit Number(int ii = 0 ) unterbindet man die implizite Konvertierung von 1 zu Number(1) im Ausdruck 1 + a
      • obige main Funktion müsste umgeschrieben werden:
    int main() {
      Number a(47), b(11);
    
      a + b; // OK
      a + Number(1); // 2nd arg converted to Number
    
    //! 1 + a; // Wrong! 1st arg not of type Number
      a - b; // OK
      a - Number(1); // 2nd arg converted to Number
    
      Number(1) - a; // 1st arg converted to Number
    }
    

    Operator

    • um den aktuellen Typ zu einem anderen Zieltyp umzuwandeln, bietet sich die Konvertierung durch einen Operator an
    • durch die Memberfunktion operator NewType() in der Klasse OldType erkläre ich, dass die implizite Konvertierung OldType zu NewType unterstützt wird
    • Beispiel:
    struct Three {};
    
    struct Four {
    
    public:
      operator Three() const { return Three; }
    
    };
    
    void g(Three) {}
    
    int main() {
    
      Four four;
      g(four);
    }
    

    Unterschied

    
    struct A{
      A( const B& );      // konverierte B zu A
    
      operator B() const; // konvertiere ein A zu B
    };
    

     

    Während beim Konvertierungskonstruktor die Zielklasse die Umwandlung bestimmt, stösst bei der Operatorkonvertierung die "Ursprungsklasse" die Konvertierung an.

    Besondere Operatoren:

    Zuweisungsoperator (operator= ):

    MyType b;
    MyType a = b; // 1 
    
    a = b;        // 2
    
    • in 1 wird aus dem bestehenden Objekt b ein neues Objekt a konstruiert
      • der Kopykonstruktor MyType( const MyType& ) wird vom Compiler verwendet
    • dahingegen werden in 2 einem fertigen Objekt a ein anderes Objekt b zugewiesen
      • operator= ( const MyType& ) schlägt zu
    • falls der Zugweisungsoperator in einer Klasse nicht definiert wird, erzeugt der Compiler einen entsprechend als Memberfunktion
    class SimpleValues{
     int a;
     double b;
    
     
     public:
     SimpleValues& operator=(const Value& rv) {
    
        a = rv.a;
        b = rv.b;
    
        return *this;
      }
    
    • enthält die Klasse hingegen Pointer, muß wie beim Kopykonstruktor die Selbstzuweisung unterbunden werden
    class NotSoSimpleValues{
      pointer* p;
      int a;
    
    NoSoSimpleValues& operator=(const NotSoSimpleValues& rv) {
        // Check for self-assignment:
        if(&rv != this) {
    
          p = new NotSoSimpleValue();
          a= rv.a;
    
        }
        return *this;
    }
    
    class CoreDump{
      pointer p;
      int a;
     public:
    
        void setA(int b){ a=b;}
    
        CoreDump& operator=(const CoreDump& rv){
          p = rv.p;
    
          a= rv.a;
          return *this;
        }
    
      ....
    }
    int main(){
      CoreDump* a= new CoreDump;
    
      CoreDump* b= new CoreDump;
    
      *a= *b;
    
      del b; // => del a;
      a->setA(10); // crash
    
    }
    
    • daher soll bei Klassen mit Pointern der Zuweisungsoperator definiert oder unterbunden werden, da der vom Compiler generierte elementweise - ohne Test auf Selbstzuweisung - kopiert

    new and delete Operator

    • beim Erzeugen eines neuen Objekts geschehen zwei Dinge
      • mittels des Operators operator new() wird Speicher allokiert
      • der Konstruktor des Objekts wird aufgerufen
    • beim Löschen des Objekts fallen entsprechend wieder zwei Operationen an:
      • der Destruktor operator delete() wird aufgerufen
      • der Speicher wird wieder freigegeben
    • um vollkommene Kontrolle über den Speicher zu erhalten, können die beide Operatoren sowohl auf Klassenebene wie auch global überladen werden
    • Gründe fürs Überladen:
      • Performancegewinn: Oftmaliges Erzeugen/Löschen von Objekte des gleichen Typs
      • Heap Fragmentation vermeiden: Freier Speicher ist zwar noch verfügbar, liegt aber nicht mehr zusammenhängend vor
      • Echtzeitanforderung: Speicheranforderung soll in konstanter Zeit geschehen

    Global:

    Syntax
    • void* operator new(size_t)
    • void operator delete(void* )
      • der new operator sollte den entsprechenden Speicher zurückgeben oder eine std::bad_alloc Ausnahme auslösen
      • beide Operatoren arbeiten mit void Pointern, da der new Operator rohen Speicher an der Konstruktor gibt und der delete Operator rohen Speicher vom Destruktor bekommt
    Beispiel
    #include <cstdio>
    #include <cstdlib>
    
    using namespace std;
    
    void* operator new(size_t sz) {
    
      printf("operator new: %d Bytes\n", sz);
      void* m = malloc(sz);
    
      if(!m) puts("out of memory");
      return m;
    
    }
    
    void operator delete(void* m) {
    
      puts("operator delete");
      free(m);
    }
    
    class S {
    
      int i[100];
    public:
      S() { puts("S::S()"); }
    
      ~S() { puts("S::~S()"); }
    };
    
    int main() {
      puts("creating & destroying an int");
      int* p = new int(47);
    
      delete p;
      puts("creating & destroying an s");
      S* s = new S;
    
      delete s;
      puts("creating & destroying S[3]");
      S* sa = new S[3];
    
      delete []sa;
    }
    

    In new und delete sollen keine iostreams Objekte verwendet werden, da diese Speicher anfordern.

    Lokal:

    #include <cstddef> // Size_t
    #include <fstream>
    #include <iostream>
    #include <new>
    
    using namespace std;
    ofstream out("Framis.out");
    
    class Framis {
    
      enum { sz = 10 };
      char c[sz]; // To take up space, not used
    
      static unsigned char pool[];
      static bool alloc_map[];
    
    public:
      enum { psize = 100 };  // frami allowed
    
      Framis() { out << "Framis()\n"; }
      ~Framis() { out << "~Framis()... "; }
    
      void* operator new(size_t) throw(bad_alloc);
      void operator delete(void*);
    
    };
    unsigned char Framis::pool[psize * sizeof(Framis)];
    
    bool Framis::alloc_map[psize] = {false};
    
    // Size is ignored -- assume a Framis object
    void*
    Framis::operator new(size_t) throw(bad_alloc) {
    
      for(int i = 0; i < psize; i++)
    
        if(!alloc_map[i]) {
          out << "using block " << i << "... ";
    
          alloc_map[i] = true; // Mark it used
          return pool + (i * sizeof(Framis));
    
        }
      out << "out of memory" << endl;
      throw bad_alloc();
    
    }
    
    void Framis::operator delete(void* m) {
    
      if(!m) return; // Check for null pointer
      // Assume it was created in the pool
      // Calculate which block number it is:
    
      unsigned long block = (unsigned long)m
        - (unsigned long)pool;
    
      block /= sizeof(Framis);
      out << "freeing block " << block << endl;
    
      // Mark it free:
      alloc_map[block] = false;
    }
    
    int main() {
      Framis* f[Framis::psize];
    
      try {
        for(int i = 0; i < Framis::psize; i++)
    
          f[i] = new Framis;
        new Framis; // Out of memory
    
      } catch(bad_alloc) {
        cerr << "Out of memory!" << endl;
    
      }
      delete f[10];
      f[10] = 0;
    
      // Use released memory:
      Framis* x = new Framis;
      delete x;
    
      for(int j = 0; j < Framis::psize; j++)
    
        delete f[j]; // Delete f[10] OK
    }
    
    • durch das Überladen erzeugt man implizit statische Memberfunktionen
    • unsigned char Framis::pool[psize * sizeof(Framis)]; legt den Speicher an
    • bool Framis::alloc_map[psize] = {false}; führt dann über diesen Buch, indem jeder unbenutzt Speicherblock auf false gesetzt wird
    • der sogenannten Aggregate Inizialiserungs Trick bewirkt, dass alle restlichen Elemente von Framis::alloc[] default Initialisiert werden, da das erste Element initialisiert wurde
    • im new Operator wird so nach einem unbenutzten Speicher gesucht und entweder die Adresse des ersten zu Verfügung stehen Bereichs zurückgegeben oder eine bad_alloc Exception ausgelöst, wie in der Signatur erklärt
    • der delete Operator ermittelt aus der Adresse des freizugebenden Speichers desssen Index und markiert den Speicherbereich als frei
    • im Gegensatz zum globen new und delete Operator dürfen in den lokalen Formen iostreams verwendet werden
    • für arrays vom Typ Framis werden die globalen new und delete Operatoren verwendet

    Arrays:

    #include <new> // Size_t definition
    #include <fstream>
    using namespace std;
    
    ofstream trace("ArrayOperatorNew.out");
    
    class Widget {
      enum { sz = 10 };
    
      int i[sz];
    public:
      Widget() { trace << "*"; }
    
      ~Widget() { trace << "~"; }
      void* operator new(size_t sz) {
    
        trace << "Widget::new: "
             << sz << " bytes" << endl;
        return ::new char[sz];
    
      }
      void operator delete(void* p) {
    
        trace << "Widget::delete" << endl;
        ::delete []p;
    
      }
      void* operator new[](size_t sz) {
        trace << "Widget::new[]: "
    
             << sz << " bytes" << endl;
        return ::new char[sz];
    
      }
      void operator delete[](void* p) {
    
        trace << "Widget::delete[]" << endl;
        ::delete []p;
    
      }
    };
    
    int main() {
      trace << "new Widget" << endl;
    
      Widget* w = new Widget;
      trace << "\ndelete Widget" << endl;
    
      delete w;
      trace << "\nnew Widget[25]" << endl;
    
      Widget* wa = new Widget[25];
      trace << "\ndelete []Widget" << endl;
    
      delete []wa;
    }
    
    • new und delete Deklaration werden um Klammern [] ergänzt
    • das Programm erzeugt folgende Ausgabe:
      new Widget
      Widget::new: 40 bytes
      *
      delete Widget
      ~Widget::delete
      
      new Widget[25]
      Widget::new[]: 1004 bytes
      *************************
      delete []Widget
      ~~~~~~~~~~~~~~~~~~~~~~~~~Widget::delete[]
      
    • in den 4 zusätzlicen Bytes wird die zusätzliche Information über die Objektanzahl gespeichert
    • dies ist aus der Unterschied zwischen delete Widget= und ==delete []Widget, a im ersten Fall Speicher für ein Widget Objekt freigegeben wird und hingegen im zweiten Fall die 4 zusätzlichen Bytes genutzt werden, um das Array von Objekten richtig freizugeben
    • falls der Speicheralloziierung in new Operator fehlschlägt, wird der entsprechende Konstruktor nicht aufgerufen:
    #include <iostream>
    #include <new> // bad_alloc definition
    
    using namespace std;
    
    class NoMemory {
    public:
      NoMemory() {
    
        cout << "NoMemory::NoMemory()" << endl;
      }
      void* operator new(size_t sz) throw(bad_alloc){
    
        cout << "NoMemory::operator new" << endl;
        throw bad_alloc(); // "Out of memory"
    
      }
    };
    
    int main() {
      NoMemory* nm;
    
      try {
        nm = new NoMemory;
      } catch(bad_alloc) {
    
        cerr << "Out of memory exception" << endl;
      }
    }
    

    Placement new and delete:

    Gründe für placement new
    • das Objekt an einer spezifischen Stelle zu positionieren
    • verschiedene Speicherbereiche zu benützen, wenn new aufgerufen wird
    • shared memory
    • Allokieren von Arrays und die damit verbundene Verwaltungsinformation
    • Zuweisungsoperator für Klassen mit Referenzen
    struct A{
    
    A( Foo& foo ):my_FooReference(foo){};
    
    Foo& m_myFooReference;
    
    A& operator=( const A& copyFrom ){
    
      if(this != &copyFrom)
      {
        // explizite Destruierung
        this->~A();
    
        // erzeuge in this Speicher
        new(this) A(copyFrom);
      }
    
      return *this;
    }
    
    Syntax

    void* operator new(size_t, void *point)Neben dem impliziten ersten Argument kann die Adresse eines Speichbereichs oder eine Referenz einer Speicher allozierenden Funktion oder Objekts übergeben werden.

    • Folgende beide Ausdrück sind äquivalent:
    void* raw = allocate(sizeof(Foo));                    
    
    Foo* p = new(raw) Foo();                                Foo* p= new Fow();
    
    Beispiel

    Alloziere ein Objekt an einer vorgegebenen Speicherstelle.

    #include <cstddef> // Size_t
    #include <iostream>
    
    using namespace std;
    
    class X {
      int i;
    
    public:
      X(int ii = 0) : i(ii) {
    
        cout << "this = " << this << endl;
      }
      ~X() {
    
        cout << "X::~X(): " << this << endl;
      }
      void* operator new(size_t, void* loc) {
    
        return loc;
      }
    };
    
    int main() {
    
      int l[10];
      cout << "l = " << l << endl;
    
      X* xp = new(l) X(47); // X at location l
    
      xp->X::~X(); // Explicit destructor call
      // ONLY use with placement!
    }
    
    • delete xp kann hier nicht angewandt werden, da xp nicht auf dem heap angelegt wurde
    • vgl. hierzu folgende äquivalente Ausdrücke:
    
    delete xp;          xp->~X();
    
                        operator delete(xp);
    

     

    • placement new ist der einzige legitime Grund den Destruktor explizit aufzurufen
    • falls der Konstruktor von X fehlschlägt, entsteht ein Speicherloch, denn delete X kann vom Laufzeitsystem nicht aufgerufen werden, da dies keine entsprechender Destruktor zum placement new ist
    • stelle einen entsprechenden placement delete Operator bereit, der die zum placement new korrespondierende Arbeit erledigt
    Syntax

    void operator delete(size_t, void* loc)

    #include <iostream>
    int flag = 0;
    
    typedef unsigned int size_t;
    
    int test[10];
    
    void operator delete(void* p, int ){
    
      // int ist nur ein dummy Argument für die Korrespondenz zum placement new
      flag = 1;
    }
    
    void* operator new(size_t s, int i){
    
      return &test[i];
    }
    
    class A {
    public:
    
      A() {throw -37;}
    };
    
    int main(){
    
      try {
        A* p = new (0) A;
    
      }
      catch (int i) {
      }
    
      std::cout << "flag: " << flag << std::endl;  
    }
    
    • MOVED TO... nun verhält sich placement new Standardkonform, den der Compiler erzeugt aus einem einfachen Aufruf der Form Foo* p = new Foo() semantisch folgenden Code:
     Foo* p;
    
     
     // Exception bad_alloc oder einen Pointer auf den angeforderten Speicher
     void* raw = operator new(sizeof(Foo));
    
     //fange jede Execption, die durch den Konstruktor ausgelöst wird
    try{
       p = new(raw) Foo();  
     }
    
     catch (...) {
       // gib den Speicher frei und "rethrow"
       operator delete(raw);
    
       throw;  
     }
    
  •  

    Reactor Pattern

    Um was gehts

    Das Reaktor Pattern erlaubt es, einer ereignis-getriebenen Anwendung eine oder mehrere Client Anfragen gleichzeitig anzunehmen und auf verschiedene Serviceanbieter zu verteilen ( demultiplex and dispatch ) .

    Auch bekannt unter

    • Dispatcher
    • Notifier

    Beispiel

    • Ein Logging Server erhält mehrere Anfragen gleichzeitig und muß sie auf die verschieden Ausgabegeräte verteilen:
      NetworkLogging.gif
    • Eventhandling ins GUIs

    Anforderungen

    Ein Server soll mehrere Clientanfragen gleichzeitig beantworten können. Jede Clientanfrage besitzt eine eindeutige Indikation, die sie einem Serviceprovider zuordnen läßt. Folgende Bedingungen müssen für die Applikation gewährleistet sein:
    • sie soll nicht blockieren
    • sie soll auf maximalen Durchsatz ausgelegt sein und somit unnötiges Kontext wechseln, Daten synchronisieren oder Daten kopieren vermeiden
    • sie soll einfach um neue und verbesserte Service erweitert werden können
    • sie soll ohne komplexe multithreading und synchronisations Mechanismen auskommen

    Lösung

    Führe für jeden Servicetyp, den die Applikation anbietet, einen Handler ein. Dieser Eventhandler verarbeitet die spezifische Clientanfragen. Registriere den Handler beim Reaktor, der einem synchronen Event Verteiler (event demultiplexer) unterhält um auf eingehende Events zu reagieren. Wenn ein Event im synchronen event demultiplexerauftritt, benachrichtigt dieser den Reaktor, der das Event auf den angefragen Service verteilt.

    Struktur

    Handles

    • identifizieren Eventquellen wie Sockets, Filehandles oder Timersignale des OS-Systems
    • die Eventquellen erzeugen Events wie connect, read, time-out an, die auf den assoziierten Handle geschoben werden
    • der Handle kann nur die entsprechende Operation vollziehen

    synchrone event demultiplex

    • der Verteiler (demultiplexer) wartet auf Indikatoren (indication events), die auf einer Menge von Handles auftreten
    • bis die indication events abgeholt werden, blockiert der event _demultiplexer
    • auf dem assozierten Handle kann nun das eintreffende Ereignis aufgerufen

    Event Handler

    • definiert das Interface um indication events zu prozessieren
    • deklarieren die Services der Applikation

    Konkrete Event Handler

    • verarbeiten indication events in einer applikationsspezifischen Art
    • definieren die Services der Applikation

    Reaktor

    • stellt ein Interface zu Verfügung, damit die Event Handler inklusiver ihrer assozierten Handles registrieren und entfernen kann
    • der Reaktor benützt den synchronen Verteiler (event demultiplexer) um auf die Indikatoren (indicaton events) der Handles zu warten
    • beim Auftreten eines Indikators (indication events) ordnet der Reaktor dies Ereugnis dem entsprechenden Ereignis Handler zu
    • nach der Zuordnung des Ereignis an den entsprechenden Ereignis Handler, ruft ( dispatch ) der Reaktor die assozierte Methode auf dem Event Handler auf
    • der Reaktor startet und unterhält die event loop der Applikation
    Nicht die Applikation, sonder der Reaktor wartet auf indication events, die er auf die entsprechenden konkreten Event Handler verteilt ( demultiplex ) und dann deren assozierte Hook Methode aufruft ( dispatch ). Als Applikationsentwickler gilt es die spezifischen Event Handler zu implementieren und sie beim Reaktor zu registrieren.
    Der Reaktor als Framework stellt eine Ablaufumgebung für die Eventverarbeitung bereit. Diese inversion of control - die Applikation wird durch den Reaktor gesteurt - wird als Hollywood Prinzip bezeichnet.
    Don't call me, we call you.

    • Reaktor Klassendiagramm:
      reactorClassDiagram.gif

     

    Umsetzung

    Timer mit twisted

    Der Reaktor reagiert auf Zeittakte. Diese Ereignisse werden auf die registrierten Handler abgebildet. Sobald die Eventloop des Reaktors mittels reactor.run() gestartet wird, können die Ereignisse verarbeitet werden.
    import time

    from twisted.internet import task
    # http://twistedmatrix.com/trac/browser/trunk/twisted/internet/task.py
    from twisted.internet import reactor

    # http://twistedmatrix.com/trac/browser/trunk/twisted/internet/reactor.py

    # define handler as object
    class Handler():

    def __init__(self, Id ):

    self.__id= Id

    def __call__(self):

    print "Handler with id %s" % self.__id
    print "at %d:%d:%d%s\n" %(time.localtime()[3:6] + ( str(time.time() % 1)[1:] ,))


    # register handler as callable object
    l1 = task.LoopingCall(Handler(1))
    # start the task

    # start calls implicit reactor.callLater(... ) to reschedule the task ( fire the time event )
    l1.start(0.3) # call every 0.3 seconds

    l2 = task.LoopingCall(Handler(2))

    l2.start(1) # call every second

    # running the event loop
    reactor.run()

    Timer mit ACE

    • starte zwei Timer cb1 und cb2, die durch Signale SIGTSTP und SIGINT um den Faktor 10 abgebremst werden können
    Die ACE Variante ist deutlicher verboser, da hier auf Time- und Signalevents mit den entsprechenden CB und Signalhandler reagiert wird. Insbsondere sorgt der TimerDispatcher für das explizite Feueren der Timeevents.
    Durch die schedule Methode des Timers bzw. die register_handler des Reactors werden die Handler registriert. Während der CB Handler auf Timeevents mit handle_timeout reagiert, reagiert der Signalhandler auf Signalevents mit handle_signal. Beide Eventhandler werden durch die gleichen Verteiler bedient ( select ). Die ACE_Timer_Queue bzw. der konkrete Implementierung ACE_Timer_Heap merkt sich die die zukünftigen Zeitpunkte, zu denen sie expire an den Verteiler schickt.
    Die Methode wait_for_event startet die Eventloop, die dann auf die Timer- und Signalevents reagiert.
    #include "ace/Timer_Queue.h"
    #include "ace/Timer_Heap.h"
    #include "ace/Reactor.h"
    #include "CB.h"

    #include "SignalHandler.h"
    #include "TimerDispatcher.h"

    int main()
    {

    CB cb1, cb2;
    cb1.setID(1);
    cb2.setID(2);

    int arg1 = 1, arg2 = 2;

    ACE_Timer_Queue *timer_queue;

    ACE_NEW_RETURN(timer_queue, ACE_Timer_Heap, -1);

    // setup the timer queue

    Timer::instance()->set(timer_queue);

    ACE_Time_Value curr_time = ACE_OS::gettimeofday();

    ACE_Time_Value threeSeconds = curr_time + ACE_Time_Value(3L);
    ACE_Time_Value fourSeconds = curr_time + ACE_Time_Value(4L);

    // start in 3 seconds, each second
    long timerId1= Timer::instance()->schedule(&cb1, &arg1, threeSeconds, ACE_Time_Value(1));

    // start in 4 seconds; each 0.3 secondcs
    long timerId2=Timer::instance()->schedule(&cb2, &arg2, fourSeconds, ACE_Time_Value(0,300000));


    // Strg c
    SignalHandler *mutateTimer1= new SignalHandler( timerId1 );

    // Strg z
    SignalHandler *mutateTimer2= new SignalHandler( timerId2 );

    ACE_Reactor::instance()->register_handler( SIGINT, mutateTimer1);
    ACE_Reactor::instance()->register_handler( SIGTSTP, mutateTimer2);


    // "run" the timer.
    Timer::instance()->wait_for_event();

    return 0;

    }

     

    Dynamische Aspekte

    • Die Applikation registriert einen konkreten Eventhander beim Reaktor. Der Eventhandler drückt durch sein Implementierung aus, auf welche Art von Events er reagieren will. Typischerweise heißen die hook-Methoden handle_*, wie handle_input, handle_timeout, handle_put,... .
    • Durch eine get_handle des konkreten Eventhandlers Methode holt sich der Reaktor den spezifischen Handler.
    • Wenn alle Handles registriert sind, startet die Applikaton die Eventloop des Reaktors. Der Reaktor überwacht nun die Menge aller registrierten Handler auf das Eintreffen von indication Events .
    • Sobals ein Event auftritt, übergibt der synchrone event demultiplexer die Kontrolle an den Reaktor.
    • Der Reaktor benützt die Handles als Schlüssel, um die entsprechenden Eventhandler aufzurufen (demultiplex) und auf die hook Methode auszurufen (dispatch).
    • Die spezifische Methode des Eventhandler bearbeitet die Anfrage direkt auf dem Handle.

    Sichten des Programmierers

    • Reactor Pattern:
      reactor.jpg

    Anwendungsentwickler

    Ich will netzwerktransparent wissen, wie lange die Design Pattern Runde noch dauert? Oder ein bißchen formaler:
    Implementiere einen Server, der auf eine Browser Anfrage ( HTTP-GET ) ein html Seite schickt, die die verbleibende Zeit bis zum Ende der Design Pattern Runde darstellt. Noch formaler
    Client macht eine HTTP-GET Request right Server nimmt Request an und dispatcht sie auf den Event Handler right Event Handler schickt die Antwort zum Client Dazu müssen drei Schritte als Anwendungsentwickler und Nutzer der Reaktor Struktur implementiert werden.
    Ich verwende den BaseHTTPServervon Python.

    Request Handler implementieren

    class RequestHandler(BaseHTTPRequestHandler):


    def do_GET(self):
    import datetime
    actTime= datetime.datetime.now()

    endTime= datetime.datetime( 2007,5,22,9)

    diffSeconds= (endTime-actTime).seconds
    self.send_response(200)
    self.send_header('Content-type', 'text/html')

    self.end_headers()
    self.wfile.write("""<?xml version="1.0" ?>

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
    <html xmlns="http://www.w3.org/1999/xhtml">
    <head>

    <title>Wie lange dauerts noch?</title>
    <script src="https://scwiki.science-computing.de/twiki/pub/TWiki/ChecklistPlugin/itemstatechange.js"
    language="javascript" type="text/javascript"></script></head>

    <body>

    <h1 align="center"><font size="10" color="#FF0000">Count down</font></h1>

    <p align="center"> <font size="6"> %s Sekunden noch bis zum Ende der Design Pattern Runde.</font> </p>

    </body>
    </html>""" %(str(diffSeconds)))

    Request Handler registrieren

    srvr = HTTPServer(("",4711),RequestHandler)

    Event loop laufen starten

    srvr.serve_forever()  

    Frameworkentwickler

    Ich will ein Reaktor Framework in Python entwickeln, das auf Input Events reagiert. Exemplarisch werden vier verschiedene Typen von Input Events gleichzeitig prozessiert und die entsprechenden Nachrichten in ähnlichnamige Dateien ins home geschrieben.
    1. stdin
      • jede stdin Eingabe erzeugt ein Event, das zur Prozessierung des Events führt
    2. lesen einer Datei
      • nach dem Einlesen der Datei wird der Handler wieder entfernt
    3. lesen einer url
      • nach dem Einlesen der Internetressource wird der Handler wieder entfernt
    4. HTTP - GET Anfrage
      • bei jedem stdin Event will ich wissen, wie lange die Design Pattern Runde noch dauert; dazu müssen periodisch folgende Schritte vollzogen werden
        • registriere den Event Handler, um den ReactorPattern Server zu fragen
        • darstellen des Ergebnisses auf stderr
        • deregistrieren des Event Handlers
    ##################
    #Event Handlers
    ##################

    import os
    import socket
    import sys
    import time


    class EventHandler:

    def handle_input(self,device):
    raise NotImplementedError

    def handle_output(self,device):
    raise NotImplementedError

    def handle_exception( self,device):
    raise NotImplmentedError

    def getHandle( self ):
    return NotImplementedError

    class InputEventHandler( EventHandler ):

    def __init__(self,device,dest):

    self.handleDevice= device
    self.outFile=open(dest,"a")

    firstLine= "Read from %s with handle %s \n" % ( device.name , device.fileno() )

    self.outFile.writelines([firstLine,"\n"])


    def handle_input(self,device):


    inp= device.readline().strip()
    self.outFile.write( inp + "\n" )

    self.outFile.flush()
    myReactor.registerHandler( InputToStderrEventHandler( urllib.urlopen("http://ackbar:4711"),

    os.getenv("HOME")+"/test.WieLangNoch" ) , "r")

    def getHandle( self ): return self.handleDevice

    class FileInputEventHandler( InputEventHandler ):

    def __init__(self,device,dest):

    self.handleDevice= device
    self.outFile=open(dest,"a")

    name=""
    try:
    name= device.name
    except:

    name= device.geturl()

    firstLine= "Read from %s with handle %s \n" % ( name , device.fileno() )

    self.outFile.writelines([firstLine,"\n"])


    def handle_input( self, device ):

    for line in device.readlines():
    self.outFile.write( line.strip() + "\n" )

    self.outFile.flush()
    Reactor().removeHandler( self ,"r" )

    class InputToStderrEventHandler( FileInputEventHandler ):

    def handle_input( self, device ):

    for line in device.readlines():
    self.outFile.write( line.strip() + "\n" )

    if line.startswith("<p align"):
    message= line.split(">")[2].split("<")[0]

    sys.stderr.write( message )
    self.outFile.flush()

    Reactor().removeHandler( self,"r")





    #############

    # Reactor
    #############

    class Singleton(object):
    def __new__(cls, *args, **kwds):

    it = cls.__dict__.get("__it__")
    if it is not None:

    return it
    cls.__it__ = it = object.__new__(cls)

    it.init(*args, **kwds)
    return it
    def init(self, *args, **kwds):

    pass



    import select

    class Reactor( Singleton ):

    readHandles={}
    writeHandles={}
    exceptHandles={}


    def registerHandler( self, eventHandler,eventTypes):

    handle= eventHandler.getHandle()
    handleId= handle.fileno()


    if "r" in eventTypes: Reactor.readHandles[handleId]= (handle,eventHandler)

    if "w" in eventTypes: Reactor.Reactor.writeHandles[handleId]= (handle,eventHandler)

    if "e" in eventTypes: Reactor.Reactor.exceptHandles[handleId]= (handle,eventHandler)


    def removeHandler( self, eventHandler ,eventTypes ):

    handle= eventHandler.getHandle()
    handleId= handle.fileno()


    if "r" in eventTypes: del Reactor.readHandles[handleId]

    if "w" in eventTypes: del Reactor.writeHandles[handleId]

    if "e" in eventTypes: del Reactor.exceptHandles[handleId]


    def handleEvents( self):

    while ( 1 ):

    rHandle, wHandle,eHandle= select.select( Reactor.readHandles.keys(), Reactor.writeHandles.keys(),
    Reactor.exceptHandles.keys() )

    print "all ready handle: Reactor.readHandles: %s Reactor.writeHandles: %s Reactor.exceptHandles: %s "
    %(rHandle,wHandle,eHandle)

    for han in rHandle:
    handleDevice= Reactor.readHandles[han][0]

    eventHandler= Reactor.readHandles[han][1]
    eventHandler.handle_input( handleDevice )

    for han in wHandle:
    handleDevice= Reactor.writeHandles[han][0]

    eventHandler= Reactor.writeHandles[han][1]
    eventHandler.handle_output( handleDevice )


    for han in eHandle:
    handleDevice= Reactor.exceptHandles[han][0]

    eventHandler= Reactor.exceptHandles[han][1]
    eventHandler.handle_exception( handleDevice )

    print "Reactor:handleEvents: waiting for input "

    import urllib

    myReactor=Reactor()
    myReactor.registerHandler( InputEventHandler( sys.stdin ,
    os.getenv("HOME")+"/test.stdin" ) ,"r")

    myReactor.registerHandler( FileInputEventHandler( open("/etc/services"),
    os.getenv("HOME")+"/test.services" ) ,"r")

    myReactor.registerHandler( FileInputEventHandler( urllib.urlopen("http://www.heise.de"),
    os.getenv("HOME")+"/test.heise" ) , "r")

    myReactor.handleEvents()

    Aspekte des Reaktor Frameworks

    Event Handler Interface festlegen
    class EventHandler:        

    def handle_input(self,device):
    raise NotImplementedError

    def handle_output(self,device):
    raise NotImplementedError

    def handle_exception( self,device):
    raise NotImplmentedError

    def getHandle( self ):
    return NotImplementedError
    Reaktor implementieren
    import select

    class Reactor( Singleton ):

    readHandles={}
    writeHandles={}
    exceptHandles={}


    def registerHandler( self, eventHandler,eventTypes):

    handle= eventHandler.getHandle()
    handleId= handle.fileno()


    if "r" in eventTypes: Reactor.readHandles[handleId]= (handle,eventHandler)

    if "w" in eventTypes: Reactor.Reactor.writeHandles[handleId]= (handle,eventHandler)

    if "e" in eventTypes: Reactor.Reactor.exceptHandles[handleId]= (handle,eventHandler)


    def removeHandler( self, eventHandler ,eventTypes ):

    handle= eventHandler.getHandle()
    handleId= handle.fileno()


    if "r" in eventTypes: del Reactor.readHandles[handleId]

    if "w" in eventTypes: del Reactor.writeHandles[handleId]

    if "e" in eventTypes: del Reactor.exceptHandles[handleId]


    def handleEvents( self):

    while ( 1 ):

    rHandle, wHandle,eHandle= select.select( Reactor.readHandles.keys(), Reactor.writeHandles.keys(),
    Reactor.exceptHandles.keys() )

    print "all ready handle: Reactor.readHandles: %s Reactor.writeHandles: %s Reactor.exceptHandles: %s "
    %(rHandle,wHandle,eHandle)

    for han in rHandle:
    handleDevice= Reactor.readHandles[han][0]

    eventHandler= Reactor.readHandles[han][1]
    eventHandler.handle_input( handleDevice )

    for han in wHandle:
    handleDevice= Reactor.writeHandles[han][0]

    eventHandler= Reactor.writeHandles[han][1]
    eventHandler.handle_output( handleDevice )


    for han in eHandle:
    handleDevice= Reactor.exceptHandles[han][0]

    eventHandler= Reactor.exceptHandles[han][1]
    eventHandler.handle_exception( handleDevice )

    print "Reactor:handleEvents: waiting for input "
    Um ein Eventhandler zu registrieren wird zusätzlich der Event Typ benötigt, für den sich der Event Handler interessiert.
    Durch die Registratur des Eventhandler ist es möglich, über den Filehandle ( z.B.: stdin = 0 ) sowohl das Fileobject, die Eventsource und den Eventhandler, die Anwendungslogik zu erhalten.
    In handle Events bedient sich der Reaktor dem nativen select Befehl um auf relevante Events registrieren zu können. handleEvents stellt die Eventloop des Reaktor dar, die, einmal gestartet, immer auf eingehende Events lauscht.

    Ausgabe, abhängig von den registrierten Event Handles

    • stdin wird registriert
      python reactorInput.py
      4
      all ready handle: Reactor.readHandles: [0] Reactor.writeHandles: [] Reactor.exceptHandles: []
      Reactor:handleEvents: waiting for input
    Erst durch die Eingabe der Zahl 4 wird die Eventloop prozessiert. Ein Eingabe Event Reactor.readHandles: [0]liegt nun vor.
    • stdin, Datei und Url Request werden registriert
      python reactorInput.py
      all ready handle: Reactor.readHandles: [4, 6] Reactor.writeHandles: [] Reactor.exceptHandles: []
      Reactor:handleEvents: waiting for input
      4
      all ready handle: Reactor.readHandles: [0] Reactor.writeHandles: [] Reactor.exceptHandles: []
      Reactor:handleEvents: waiting for input
    Die Ressourcen file und url sind beim Starten der Eventloop registriert, daher werden sie sofort prozessiert all ready handle: Reactor.readHandles: [4, 6] Reactor.writeHandles: [] Reactor.exceptHandles: [] . Da ich sie explizit deregistriere sind sie bei in dem Eintreten eines stdin-Events nicht mehr vorhanden all ready handle: Reactor.readHandles: [0] Reactor.writeHandles: [] Reactor.exceptHandles: []. Der Reaktor prozessiert nun nur noch den Filedescriptor 0, also stdin.
    • stdin, Datei , Url und HTTP-GET Request werden registriert
      all ready handle: Reactor.readHandles: [4, 6] Reactor.writeHandles: [] Reactor.exceptHandles: []
      Reactor:handleEvents: waiting for input
      4
      all ready handle: Reactor.readHandles: [0] Reactor.writeHandles: [] Reactor.exceptHandles: []
      Reactor:handleEvents: waiting for input
      all ready handle: Reactor.readHandles: [4] Reactor.writeHandles: [] Reactor.exceptHandles: []
      49661 Sekunden noch bis zum Ende der Design Pattern Runde.Reactor:handleEvents: waiting for input
    Die stdin Abfrage registriet nun einen neuen Eventhandler, der die mir die Frage beantwortert: Wie lange dauert noch die Design Pattern Runde? Dieser Request erhält wieder den Filedescriptor 4. all ready handle: Reactor.readHandles: [4] Reactor.writeHandles: [] Reactor.exceptHandles: []

    Implementierung

    Die Implementierung des Reaktor Patterns lässt sich in zwei Schichten unterteilen. Die Frameworkschicht, die die applikationsunabhängige demultiplex/dispatch Infrastruktur zur Verfügung stellt und die Applikationschicht, die die konkreten Eventhandler liefert. In der klassischen, einfachsten Form, geschieht das ganz Eventhandling in einem Prozeß.

     Definiere das Event Handler Interfaces

    Die Methoden des Event Handlers legen das Servcie-Interface des Reaktor Frameworks fest.
    1. bestimme den Typ des dispatchingZiels
      • verwende eine Event Handler Objekt oder eine Event Handle Funktion
    2. bestimme die Event Handling dispatching Strategie
      1. dispatch auf eine einzelne Methode
        ...
        virtual void handlle_event( Handle handle, Event_Type et)= 0;
        ...
      2. dispatch auf mehrere Methoden
        ...
        virtual void handle_input( Handle handle )=0;
        virtual void handle_output( Handle handle )=0;
        virtual void handle_timeout( Handle handle )=0;
        ...
      • das eine Methode Interface erlaubt es einfach, das Framework um neue Eventtypen zu erweitern
      • während bei handle_event die ganze Verteilungsstrategie mittels Bedingungen auf Applikationsebene definiert werden muß, geschieht der dispatch auf dem reichhaltigeren Interface automatisch auf Frameworkebene
      • insbesondere ist bei feingranularen Dispatch möglich, spezielle hook Methoden in konkreten Event Handlern zu überschreiben

     Definiere das Reaktor Interface

    Die Applikation nutzt einerseits das Reaktor Interface um die spezifischen Event Handler zu de/registrieren und andererseits die Event Loop zu starten. Gerne wird das Reaktor Interface als Singleton implementiert, das die Anfragen an die Reaktor Implementierung delegiert. Neben dem Event Handler erhält erhält die register_handler als zweites Argument den Event Type als Argument, für den sie sich interessiert.
    void Select_Reactor_Implementation::register_handler( EventHandler* eventHandler, Event_Type event_type )

     Implementiere das Reaktor Interface

    • entkopple das Reaktor Interface von seiner Implementierung durch eine Brücke right mehrere verschiedene Implementierung können unterstützt werden ( select, poll, WaitFormMultipleObjects , GuiEventLoops ,... )
    • wähle einen synchronen event demutliplexer aus
    int select( u_int max_handle_plus_1 , 
    fd_set *read_fds, fd_set * write_fds, fd_set *except_fds,

    timeval *timeout);
    • implementiere ein demultiplexing table
      • ein Eintrag soll von der Form < handle, event_handle, indication event type > sein, wobei handle als Schlüssel für den Event Handler bei einem indication event ( connect, expire, read,... ) verwendet wird
    • definiere die Reaktor Implementierung
    # select Server( only demultiplexing ) 
    def get_request(self):

    while self._running:
    log.info('select on listen socket')
    # demultiplex

    rs, ws, es = select.select([self.socket], [], [], 10)

    if rs:
    log.info('accepting new connection')
    # socketobject and address
    return self.socket.accept()
    log.info('ending request loop')

    return (None, None)

     Bestimme die Anzahl der Reaktoren, die man benötigt

    • in der Regel sollte der Reaktor ein Singleton sein, jedoch erlaubt win32 nur 64 Handles pro Reaktor
    • aus Echtzeitforderungen kann es nötig sein mehrere Reaktoren gleichzeitig laufen zu lassen; Trennung der Reaktoren nach Eventtypen

     Implementiere die konkreten Eventhandler

    • sie stellen die Anwendungslogik dar, im Gegensatz zu dem bisher vorgestellten Reaktor-Framework
    • statte die Eventhandler gegebenfalls mit einem Zustand aus; vgl. Beispiel Timer mit ACE
    • implementiere die Eventhandler Funktionalität

    Kritik

    • Vorteile
      • klare Trennung von Framework- und Applikationslogik
      • Modularität von eventgetriebenen Anwendungen durch verschieden Eventhandler
      • Portabilität durch Trennung von Interface und Implementierung des Reaktors
      • einfache Parallelität durch den synchronen event demutliplexers
    • Nachteile
      • setzt einen event demultiplexer voraus
      • Durchsatzprobleme bei lang laufenden Event Handler in single Threaded Applikation, den der Event Handler blockiert den Reaktor
      • schwierig zu debuggen und zu testen durch die inversion of control

    Verwendete Patterns - Techniken

    • ObserverPattern
      • der Event Handler wird informiert, sobald ein für ihn spezifisches Event auftritt
    • BridgePattern
      • der Reaktor hält sich eine spezifische Reaktor Implementierung, an die er die Aufrufe delegiert
    • TemplateMethodePattern
      • die handle_* Methoden als hook Methoden werden wohl in statisch typisierten Programmiersprachen in einer definierten Reihenfolge prozessiert
    • double Dispatch: registerHandler right getHandle

     

  •  

    Speicherverwaltung mit auto_ptr

    nach "The C++ Standard Library" von Nicolai M. Josuttis

    • Die C++ standard library enthält "smart pointern" - die auto_ptr -, um die Speicherverwaltung zu vereinfachen.
    • auto_ptr reichen das Interface des Pointers durch, den sie besitzen
    • die auto_ptr folgen dem RAII Idiom "resource acquisition is initialization" , bei dem die Resssourcebeanspruchung und Ressourcefreigabe an lokale Objekte gebunden wird (vgl. dazu Bjarne Stroustrup)

    Motivation

    void f(){
    ClassA* ptr= new ClassA;

    // tue etwas mit ptr
    delete ptr;
    }
    • dieser Code hat zumindestens zwei Probleme:
      • return statement zwischen new und delete
      • eine Ausnahme kann geworfen werden
    • beide Punkte führen zu einem Speicherloch
    • Lösungsversuch
    void f(){

    ClassA* ptr= new ClassA;
    try{
    // tue etwas mit ptr

    }
    catch( ... ){
    delete ptr;

    throw;
    }
    delete ptr;
    }
    • der Code wird komplizierter und redundant
    • falls ein neues Objekt entsprechend gesichert werden soll, wird' s noch komplizierter
    • das Löschen eines null Pointers hat keine Auswirkung, falls schon das Speicher allozieren schieft geht
    • MOVED TO... auto_ptr als Lösung:
    void f(){
    std::auto_ptr<classA> ptr(new ClassA);

    ....
    };
    • auto_ptr fungiert hier als lokales Objekt
    • sobald ptr zerstört wird, zerstört auto_ptr sein Objekt new ClassA
    #include <memory>
    #include <string>

    void bad(){
    std::string* ptr= new std::string;
    }

    void good(){
    std::auto_ptr< std::string > ptr(new std::string);
    }

    int main(){
    bad();
    good();
    }
    • ALERT! der auto_ptr besitzt sein Objekt bzw. ein Objekt sollte maximal einen auto_ptr als Besitzer haben ALERT!
    • der auto_ptr will explizit sein Objekt erhalten:
      • std::auto_ptr ptr(new ClassA);   //OK
      • std::auto_ptr ptr= new ClassA;   //Fehler
      • std::auto_ptr ptr1( new ClassA);
      • std::auto_ptr ptr2( new ClassA); ptr1=ptr2 // OK
      • std::auto_ptr ptr; ptr= new ClassA   //Fehler

    Transfer of Ownership

    • für auto_ptr gilt das Prizip der "strict ownership":
    • MOVED TO... durch den Kopierkonstruktor und den Zuweiungsoperator wechselt der Besitz

    Kopierkonstruktor

    std::auto_ptr<ClassA> ptr1(new classA);

    std::auto_ptr<classA> ptr2(ptr1);

    Zuweisungsoperator

    std::auto_ptr<ClassA> ptr1(new classA);
    std::auto_ptr<classA> ptr2;

    ptr2= ptr1;
    • in beiden Fällen geht der Besitz an ptr2 über und ptr1 ist danach ein Nullzeiger
    • ALERT! bei beiden Operation wird das Ursprungsobjekt modifiziert; d.h. die rechte Seite der Zuweisung wird modfiziert ALERT!
    • MOVED TO... der Zuweisungsoperator und der Kopierkonstruktor erhalten nicht konstante Parameter:
    auto_ptr( auto_ptr&) throw();

    auto_ptr& operator= (auto_ptr&) throw();
    • durch
    std::auto_ptr<ClassA> ptr1(new classA);
    std::auto_ptr<classA> ptr2(new classA);

    ptr2= ptr1;
    • wird ptr2 urspüngliches Objekt gelöscht

    Source and Sink

    Funktion als Senke von Daten

    void sink( std::auto_ptr<ClassA> data);
    • sink wird zur Senke der Daten
    • sink gehört nun data und beim Beenden von sink wird data inklusive seines Besitzers gelöscht

    Funktion als Quelle von Daten

    std::auto_ptr<classA> source(){

    std::auto_ptr<classA> ptr(new ClassA);
    ....
    return ptr;
    }

    void g(){
    std::auto_ptr<classA> p;

    for ( int i=0; i<10; ++i){

    p= source();
    ....
    }
    }
    • ptr inklusiver seiner Daten werden beim rauskopieren gelöscht
    • erhält p von der Quelle source die Daten
    • durch die neue Zuweisung in der for Schleife wird p's ursprüngliche Objekt gelöscht
    • letztendliche wird auch p und sein Inhalt gelöscht, sobald p out of scope geht

    Warnungen

    • folgender Code führt zu einem Laufzeitfehler
    template < typename T >

    void doSomething( std::auto_ptr<T> p );
    ....
    std::auto_ptr <int> p( new int );

    *p= 42;
    doSomething( p );
    *p= 18;
    • der Speicher von p wird gelöscht, da sein Besitz an doSomething übergeht
    template < typename T >

    void doSomething( std::auto_ptr<T>& p );
    • die Übergabe durch eine Referenz gilt als schlechtes Design, da die Besitzverhältnisse nicht mehr geklärt sind, den sowohl der aufrufende als auch der aufgerufene scope besitzen nun den auto_ptr
    template < typename T >
    void doSomething( const std::auto_ptr<T>& p );
    • hingegen wird die Übergabe als eine konstante Referenz explizit durch den Standard unterbunden
    • MOVED TO... da alle Container die Objekte als konstante Referenz annehmen und dann intern erst mal kopieren, können diese keinen auto_ptr als Mitglied halten
    • will man den Besitzübergang des Objekts von auto_ptr unterbinden, kann man diesen als const erklären:
    std::auto_ptr<int> f(){
    const std::auto_ptr<int> p(new int);

    std::auto_ptr<int>q(new int);

    *p= 42; // OK

    p=q; // Error
    q=p; // Error
    return p; // Error

    }
    • die Zuweisung *p= 42 ist möglich, da hier nur das von p referenzierte Objekt angesprochen wird
    • hingegen setzen p=q; q=p und return p die Besitzübergabe voraus, was auf einem konstanten auto_ptr nicht möglich ist

    auto_ptr als Member

    class ClassB{
    private:
    ClassA* ptr1;

    ClassA* ptr2;
    public:
    ClassB( ClassA val1, ClassA val2): ptr1(new ClassA(val1)),ptr2(new ClassA(val2)){}

    ClassB(const ClassB& x): ptr1(new ClassA(*x.ptr1)),ptr2(new ClassA(*x.ptr2)){}

    const ClassB& operator= (const ClassB& x){

    *ptr1= *x.ptr1;
    *ptr2= *x.ptr2;

    return *this;
    }
    ~ClassB(){ delete ptr1; delete ptr2 }

    ...
    };
    • falls das zweite new in beiden Konstruktoren eine Ausnahme wirft, entsteht eine Speicherloch, da der Destruktor erst dann aufgerufen wird, wenn der Konstruktor fertig durchgeführt wurde
    • hier bieten auto_ptr eine einfache Lösung
    class ClassB{
    private:
    const std::auto_ptr<ClassA> ptr1;
    const std::auto_ptr<ClassA> ptr2;

    public:
    ClassB( ClassA val1, ClassA val2): ptr1(new ClassA(val1)),ptr2(new ClassA(val2)){}

    ClassB(const ClassB& x): ptr1(new ClassA(*x.ptr1)),ptr2(new ClassA(*x.ptr2)){}

    const ClassB& operator= (const ClassB& x){

    *ptr1= *x.ptr1; //
    *ptr2= *x.ptr2;

    return *this;
    }
    ...
    };
    • der Kopierkonstruktor und der Zuweisungsoperator müssen implementiert werden, da die vom Compiler erzeugten die beiden auto_ptr direkt modifizieren würde, was nicht erwünscht und nicht möglich ist:
    const ClassB& operator= (const ClassB& x){

    ptr1= x.ptr1;
    ptr2= x.ptr2;

    return *this;
    }

    Manipulation der Daten

    release

    T* auto_ptr::release() throw();
    • gibt das Objekt wieder frei
    • als Returnwert erhält man die Adresse des Objekts oder den Nullzeiger

    reset

    void auto_ptr::reset( T* ptr= 0) throw();
    • der auto_ptr wird mit ptr reinitialisiert
    • das ursprüngliche Objekt wird gelöscht
    • *this ist nun der Besitzer des Objekts

    Missbrauch

    auto_ptr' s können ihren Besitz nicht teilen:

    void tschüs( auto_ptr <int > );

    int* i =new int;
    std::auto_ptr <int> p1(i);

    std::auto_ptr <int> p2(i);
    tschüs(p2);
    *p1= 30; // undefiniertes Verhalten

    auto_ptr' s können keine arrays verwalten, da dieser delete (nicht delete []) aufruft

    auto_ptr' s erfüllen nicht die Anforderungen von Containerelementen

    Quelle und Ziel einer Zuweisung- oder Kopieroperation ergibt bei auto_ptr verschiedene Objekte MOVED TO... das initiale Kopieren im Container hätte fatale Folgen für die Quelle der Operation

    Ergänzungen:

  •  

    Standard Template Library

    oder Wie man das Strategy Pattern mit Templates umsetzt.
    TIP Die Konfiguration von Algorithmen mittels Objekten bezeichnet man gerne als Strategy, hingegen die Konfiguration von Typen mittels Subtypen als Traits. Die STL basiert im Wesentlichen auf drei Komponenten, den Algorithmen, den Datencontainern und den Iteratoren.
    Die generischen Algorithmen wirken auf den Elementen der Datencontainer durch die Iteratoren.
    Die Iteratoren stellen das verbindende Elemente zwischen den Datencontainern und den Algorithmen dar.

    Im Gegensatz zur objektorientierten Orientierung, in der die Daten und Algorithmen in einer Klasse gekapselt sind, sind in der STL die Algorithmen von den Datenstrukturen getrennt.

    Abgerundet wird die STL einerseits durch Funktionspointer bzw. Funktoren, um die Algorithmen zu konfigurieren und andererseits durch Iteratoradaptoren, um die Iteratoren universeller einzusetzten.
    Durch den Erweiterung des C++ Standards um anonyme Funktionen, bzw. lambda Funktionen, unterstützt die STL neben dem generischen auch insbesondere das funktionaleProgrammieren.

    Beispiel

    Den STL Algorithmus accumulate gibt es ein zwei Variationen:
    1. T accumulate( InputIterator beg, InputIterator end, T initValue ):
    2. T accumulate( InputIterator beg, InputIterator end, T initValue , BinaryFunc op):
    Beide Algorithmen erwarten
    • zwei Iteratoren beg und end
    • ein Initialisierungswert initValue, der dem Returntyp entsprechen muß
    Die zweite Variation kann über einen binären Funktor parametrisiert werden.
    MOVED TO...Als Ergebnis erzeugt die erste Variante für die Elemente a1 a2 a3...
    • (((initValue + a1 ) + a2 ) + a3 ) + ..
    und die zweite Variante
    • (((initValue op a1 ) op a2 ) op a3 ) op ..
    Das folgende Beispiel soll den generischen Ansatz der STL mittels Templates hervorheben, denn es variiert in drei Aspekten:
    1. Elemente der Container
    2. Typ des Containers
    3. Verhalten des Algorithmus
    #include <functional>
    #include <iostream>
    #include <numeric>

    #include <string>

    #include <set>
    #include <vector>

    #include "functor.hpp"

    #include "IntSequence.h"

    int main(){

    std::vector<int> intsInVector;

    std::generate_n( std::back_inserter( intsInVector ), 10, IntSequence(0));

    std::cout << "intsInVector :";
    std::copy( intsInVector.begin(), intsInVector.end(),

    std::ostream_iterator < int > ( std::cout, " "));

    std::vector< std::string > stringsInVector;
    stringsInVector.push_back("one");

    stringsInVector.push_back("two");
    stringsInVector.push_back("two");
    stringsInVector.push_back("three");

    stringsInVector.push_back("four");
    stringsInVector.push_back("five");
    stringsInVector.push_back("six");

    stringsInVector.push_back("seven");
    stringsInVector.push_back("eight");
    stringsInVector.push_back("nine");

    stringsInVector.push_back("ten");

    std::cout << "\n" << "stringsInVector :";

    std::copy( stringsInVector.begin(), stringsInVector.end(),

    std::ostream_iterator < std::string> ( std::cout, " "));

    std::cout << "\n\n\n---------- Variation der Elemente: ----";

    std::cout << "\n" << "intsInVector sum: " ;

    std::cout << std::accumulate( intsInVector.begin(), intsInVector.end(),0 ) ;

    std::cout <<"\n" << "stringsInVector sum: ";
    std::cout << std::accumulate( stringsInVector.begin(), stringsInVector.end() ,std::string("") );

    std::cout << "\n\n\n----------- Variation des Containers: ----" << std::endl;

    std::set<int> intsInSet( intsInVector. begin(), intsInVector.end() );

    std::cout << "intsInSet sum: ";
    std::cout<< std::accumulate( intsInSet.begin(), intsInSet.end() ,0 );

    std::set< std::string > stringsInSet( stringsInVector. begin(), stringsInVector.end() );

    std::cout <<"\n" << "stringsInSet sum: ";
    std::cout << std::accumulate( stringsInSet.begin(), stringsInSet.end(),std::string("") );

    std::cout << "\n\n\n----------- Variation des Algorithmus: ----" << std::endl;

    std::cout <<"\n" << "10 Faktultät :";
    std::cout << std::accumulate( intsInVector.begin(), intsInVector.end(),1, std::multiplies< int > () ) ;

    std::cout <<"\n" << "Norm :";
    std::cout << std::sqrt( static_cast<double> ( std::accumulate( intsInVector.begin(), intsInVector.end(), 0, addQuad< int > () ) ) ) ;

    std::cout << std::accumulate( stringsInVector.begin(), stringsInVector.end() ,std::string("\nPurify "),addSep<std::string> (std::string(" : " )));

    std::cout << std::accumulate( stringsInVector.begin(), stringsInVector.end() ,std::string("\nPurify title "), addSepAndTitle<std::string> (std::string(" : " )) );

    };
    Hierzu die entsprechende Ausgabe.

     

    intsInVector :1 2 3 4 5 6 7 8 9 10 
    stringsInVector :one two two three four five six seven eight nine ten


    ---------- Variation der Elemente: ----
    intsInVector sum: 55
    stringsInVector sum: onetwotwothreefourfivesixseveneightnineten


    ----------- Variation des Containers: ----
    intsInSet sum: 55
    stringsInSet sum: eightfivefournineonesevensixtenthreetwo


    ----------- Variation des Algorithmus: ----

    10 Faktultät :3628800
    Norm :19.6214
    Purify : one : two : two : three : four : five : six : seven : eight : nine : ten
    Purify title : One : Two : Two : Three : Four : Five : Six : Seven : Eight : Nine : Ten

    Container

    container-hierarchy.gif
    Die Container sind Template, die über ihren Datentyp parametrisiert werden.
    std::vector<int> myInt;
    std::map< std::string, int > myTelbook
    • es gibt zwei Typen von Containern
      • sequentielle
        • vector
          • kontinuierlicher Speicherbereich
          • für schnelles löschen und hinzufügen am Vektorende optimiert
          • _intelligentes Array_
          • erlaubt wahlfreien Zugriff
        • deque
          • double ended queue
          • für schnelle Manipulation am Containeranfang optimiert
          • erlaubt wahlfreien Zugriff
        • list
          • doppelt verkettete Liste
          • für schnellen Manipulation im Containerkörper optimiert
          • kein wahlfreien Zugrff
      • assoziative
        • map, multimap
          • assoziatives Array mit geordnente Schlüsseln
          • unterstützt das Interface eines Dictionary
          • als binärer Suchbaum implementiert
          • Zugriffsgeschwindigkeit im Gegensatz zum Dictionary ist logarithmisch
          • multimaps erlauben mehrfahre Werte zu einem key
        • set, multiset
          • wie map bzw. multimap ohne values
    • die Typen std::string und das klassische C-Array sind weitere, klassische Container

    Iteratoren

    iterator.gif
    • jeder Containertyp stellt den richtigen Iterator in zwei Versionen zur Verfügung
    std::vector<int> :: itertor myIntIterator;

    std::vector<int> :: const_itertor myConstIntIterator;
    std::map<std::string, int > :: iterator myTelbookIterator;

    std::map<std::string, int > :: const_iterator myConstTelbookIterator;
    • Iteratoren bietet zum iterieren das gleiche Interface wie Pointer an
      • Operator * , ++ , == , != und =
    • sie sind in dem Sinne intelligent, daß sie das Iterieren über deutlich komplexere Strukturen erlauben
    • mittels begin() und end() erhält man die Position des ersten Elements des Containers bzw. die hinter des letzten Elements des Containers
    • der folgende Ausdruck iteriert über alle Element einschließlich stringsInVector.begin() und ausschließlich stringsInVector.end()
    std::copy( stringsInVector.begin(), stringsInVector.end(),

    std::ostream_iterator < std::string > ( std::cout, " "));
    • die Mächtigkeit des Iterators ist abhängig vom Containertyp
      • die Iteratoren der assoziativen und sequentiellen Container sind zumindestens bidirektionale Iteratoren
      • die Container vector und deque erlauben darüber hinaus den wahlfreien Zugriff; sie werden als random access Iteratoren bezeichnet
    • folgende Bild soll die Mächtigkeit der Iteratoren abhängig vom Iteratortyp verdeutlichen
    • img3.png

    Algorithmen

    • ALERT! Im Gegensatz zu dem objektorientierten Paradigma, in dem Daten und Algorithmen gekapselt sind, sind die Algorithmen in der STL von ihren zu verarbeitenden Datenstrukturen getrennt. MOVED TO... generische Programmierung
    • Algorithmen sind in C++ das Mittel das Wahl um Daten unter vorgegebenen Aspekten zu manipulieren.
    • Sie lassen sich grob durch ihre Wirkung auf die Containern und insbesondere deren Elemente klassifizieren.

    Typen

    1. nonmodifying
      • equal, count, min_element, find...
    2. modifying
      • copy, transform, merge, fill, generate, replace...
    3. removing
      • remove, unique...
    4. mutating
      • reverse, rotate, next_permutation, random_shuffle...
    5. sorting
      • sort, stable_sort, partial_sort, nth_element
    6. on sorted ranges
      • binary_serach, includes, lower_bound, upper_bound, set_union...
    7. numeric
      • accumulate, partial_sum, inner_product...

    Charakteristika

    wirken auf halboffenen Intervallen: [Anfangsiterator,Enditerator[

    // vec= 10 8 9 0 -1
    int maxElement= *max_element( vec.begin(), vec.end() );

    // maxElement= 10

    stellen ihre Ergebnisse mittels Iteratoren zur Verfügung

    • das gesuchte Element wird mittels Iterator zurückgegeben
    • der Zielcontainer wird ab einer Iteratorpostion geschrieben
    // vec= 1 2 3 4 5 6 7
    // add 10 to each element of vec
    transform( vec.begin(), vec.end(), // source

    vec.begin(), // destination
    bind2nd( plus<int>(),10 )); // operation

    // vec= 11 12 13 14 15 16 17
    • MOVED TO... da Algorithmen sowohl bei der Eingabe wie auch bei der Ausgabe auf Iteratoren wirken, können die Algorithmen ineinander geschachtelt werden

    sind durch Funktoren bzw. Funktionen konfigurierbar

    • falls die Funktoren bzw. Funktionen ein boolschen Wert zurückgeben, werden sie Prädikate genannt
    • Prädikate kann man in vielen Kontexten verwenden
    unäres Prädikat
    • Suchkriterium für Elemente
    // vec= -10 9 1 3 101 8
    // find first element greater than 100
    int first= *find_if(vec.begin(), vec.end(), // source
    bind2nd( greater <int> (), 100 )); // criterion
    // first= 101
    • Anwendungskriterium des Algorithmus auf die Elemente
    //  vec= 0 1 2 3 4 5 6  // count all odd numbers
    int numOdd= count_if( vec.begin(), vec.end(), // source
    bind2nd( modulus <int> (), 2 )); // criterion
    // int= 3
    binäres Prädikat
    • als Vergleichskriterium für Elemente
    vec= 1 2 3 6 9 12 13 14

    // find four consecutive elements divisible by 3
    int ele= *search_n_if( vec.begin, vec.end() , // source

    4 , // count
    not1( bind2nd( modululs <int>(),3 ))); // criterion

    // ele= 3
    • als Verarbeitungskriterium für Elemente
    // vec= 101 100 3 4 1000
    bool lessLength(const string& s1, const string& s2){
    return s1.length() < s2.length();
    }
    // function as sorting criterium

    sort(vec.begin(), vec.end(), // source
    lessLength); // criterion

    // not stable // vec= 4 3 1001 100 1000

    lassen sich mit Iteratoradapter universeller einsetzen

    • std::ostream_iterator, std::back_inserter

    Algorithmen,

    1. die konfiguriert werden könnnen, enhalten das Anhängsel _if
    using namespace std;
    // Anzahl aller Elemente mit dem Wert 4

    int num= count(vec.begin(),vec.end(),
    4 );

    // zähle alle, die größer als 4 sind
    num= count_if( vec.begin(),vec.end(),

    bind2nd( greater<int>(),4));
    1. die, die Elemente nicht in place modifizieren, enhalten das Anhängsel _copy
    using namespace std;
    // invertiere die Reihenfolge der ersten 3 Elemente
    reverse(deque.begin(), deque.begin()+3 );

    // gib die invertierten Elemente lediglich aus
    reverse_copy( deque.begin(),deque.end() ,
    ostream_iterator<int>( cout ));

    Konfiguration über Strategien

    Die Algorithmen bieten Schnittstellen an, um sie über Strategien zu konfigurieren.

    Funktionenpointer

    Genügt die Signatur und der Returnwert der Funktion den Anforderungen des Algorithmus, kann die Funktion dazu verwendet werden
    • Elemente zu modifizieren
    double quad( double first, int second ){

    return pow( first ,second );
    }

    // cont1= 1.0 2.0 3.0 4.0 5.0 6.0
    // cont2= 2 2 2 2 2 2

    transform( cont1.begin(), cont1.end(), // first range
    cont2.begin(), // second range
    cont1.begin(), // destination range
    quad) // operation
    // cont1= 1.0 4.0 9.0 16.0 25.0 36.0

     

    • Elemente zu bewerten
    bool isPrime( int number ){

    if ( number == 0 || number == 1 ) return true;

    int divisor;
    for ( divisor = number/2 ; number%divisor = 0 ; --divisor ){;}

    return divisor == 1;
    }
    // cont= 910 .. 920
    // remove each prime number from cont and print it
    remove_copy_if( cont.begin(), cont.end(), // source
    ostream_iterator<int>( cout, ", " ), // destination
    isPrime ); // unary predicate

    // output: 907, 911, 919

    Funktoren

    • sind Objekte, die sich wie Funktionen verhalten
    • durch das Überladen des Klammeroperators unterstützen diese Objekte die Aufrufsyntax von Funktionen
    • ein minimal implementierte Funktor besitzt folgende Form
    class X{
    public:
    return_value operator() (arguments) const;

    ...
    • und implementiert
    class PrintInt {

    public:
    void operator()( int element ) const{
    cout << ", " << ;
    }
    }
    // cont= 1 2 3 4 5 6
    for_each( cont.begin(), cont.end(), // range

    PrintInt() ); // operation
    // output:, 1, 2, 3, 4, 5, 6,
    Funktoren besitzen zwei wesentliche Vorteile gegenüber Funktionenpointer.
    1. sie sind in der Regel schneller als gewöhnliche Funktionen
    2. sie besitzen einen Zustand, so daß der Aufruf des Funktors parametrisiert werden kann
    void add10( int& elem ){ elem += 10; }

    class AddValue(
    private:
    int val;
    public:

    AddValue( int v) val(v){}
    void operator()( int& elem ) { elem += val; }

    }
    // cont= 1 2 3 4 5 6
    for_each( cont.begin(), cont.end(), // range

    add10 ); // operation
    // cont= 11 12 13 14 15 16
    for_each( cont.begin(), cont.end(), // range

    AddValue(10) ); // operation
    // cont= 21 22 23 24 25 26
    for_each( cont.begin(), cont.end(), // range

    AddValue(100) ); // operation
    // cont= 121 122 123 124 125 126

    STL Erweiterung - boost

    Mit Hilfe von boost::bind, boost::function und boost::lambda unterstützt die STL das funktionale Programmieren.
    Durch Komposition von Functoren/Funktionenpointer können mächtigere Ausdrücke zusammengebaut werden
    • logische Verknüpfung
    // cont= 1 2 3 4 5 6 7 8 9 10 12
    // entferne jedes Element, das durch 2 und 3 teilbar ist
    remove_copy_if( cont.begin(), cont.end(), // source

    ostream_iterator<int>( cout, ", " ), // destination
    boost::bind( // predicate: ( i%2== 0 and i%3 == 0 )

    logical_and<bool>(), //
    boost::bind( modululs <int>(),_1,2 ), // each value must be divisible by 2

    boost::bind( modululs <int>(),_1,3 ))); // each value must be divisibel by 3

    // output: 6, 12
    • sequentielle Verknüpfung
    // cont= 10.0 100.0 1000.0
    // addiere zu jedem Wert 10 Prozent hinzu und ziehe wieder 10 Prozen ab
    transform( cont.begin(),cont.end(), // source

    ostream_iterator<int>( cout, ", " ), // destination
    boost::bind(

    multiplies<double>(),0.90, // substract 10 percent
    boost::bind<double> (

    multiplies<double>(),_1,1.10))); // add 10 percent

    // cont= 9.9 99 99
    Mit Hilfe von lambda Expressions kann die Datenmanipulation im Algorithmus definiert werden
    • logische Verknüpfungen
    // cont= 1 2 3 4 5 6 7 8 9 10 12
    // entferne jedes Element, das durch 2 und 3 teilbar ist
    remove_copy_if( cont.begin(), cont.end(), // source

    ostream_iterator<int>( cout, ", " ), // destination
    ( _1%2== 0 && _1%3== 0 )); // predicate: ( i%2== 0 and i%3 == 0 )

    // output: 6, 12
    • sequentielle Verknüpfung
    // cont= 10.0 100.0 1000.0
    // addiere zu jedem Wert 10 Prozent hinzu und ziehe wieder 10 Prozen ab
    transform( cont.begin(),cont.end(), // source

    ostream_iterator<int>( cout, ", " ), // destination
    _1 = (_1 * 1.1)*0.9 ); // operation

    // cont= 9.9 99 99
  •  

    Strategy Pattern

    Coderefaktoring

    davor

    Der folgende Beispielcode besteht fast nur aus case Anweisungen, die vom Beschäftigungsverhältnis abhängig sind.
    Er ermittelt, abhängig vom Eingabewert, die wichtigen Dinge im Leben, wie den Jahresurlaub, die Prämien, die Wochensarbeitszeit und den Lohn.
    ALERT! Code zeigen ALERT! Er schmeckt gewaltig.
    public class strategyFirst {

    public static void main(String[] args){

    if ( args.length != 1){
    System.out.println("Usage: java strategyFirst number ");

    System.exit(0);
    }

    int type= Integer.parseInt(args[0]);
    System.out.println("Annual Vacation: " + getAnnualVacation( type));
    System.out.println("Bonnification: "+ getBonification( type ) );

    System.out.println("WeeklyWorkingHourse: "+ getWeeklyWorkingHours(type));
    System.out.println("Loan: " + getLoan( type ) );

    }

    private static double getBonification( int type ){

    // something to do
    double bonUnit= 1000;
    switch ( type ){

    // boss
    case 0:
    return 10* bonUnit;

    // manager
    case 1:
    return 2* bonUnit;

    //officer
    case 2:
    return bonUnit;
    // employee + rest

    default:
    return 0;
    }
    }


    private static int getWeeklyWorkingHours( int type ){

    // something to do
    int weekUnit=60;
    switch ( type ){

    // boss
    case 0:
    return weekUnit - 40;

    // manager
    case 1:
    return weekUnit - 20;

    //officer
    case 2:
    return weekUnit;
    // employee

    case 3:
    return weekUnit + 10;
    //rest

    default:
    return weekUnit + 20;
    }
    }


    private static double getAnnualVacation( int type ){
    // something to do

    double dayUnit= 20;
    switch ( type ){

    // boss
    case 0:
    return 3* dayUnit;

    // manager
    case 1:
    return 2 * dayUnit;

    //officer
    case 2:
    return 1.5 * dayUnit;

    // employee + rest
    default:
    return dayUnit;
    }
    }



    private static double getLoan( int type ){
    //something do do

    double monthlyProfit= 100000;
    double baseSalary= 1000;

    switch ( type ){
    // boss
    case 0:

    return 5 *baseSalary + 0.5 * monthlyProfit;
    // manager

    case 1:
    return 3 * baseSalary + 0.1 * monthlyProfit;

    //officer
    case 2:
    return 2* baseSalary;

    //employee
    case 3:
    return baseSalary;
    // rest

    default:
    return 0;
    }
    }
    }
    • Was kann man tun?
      1. Einführen einer Fabrikmethode, die mir zu dem angefragten Type das entsprechende Beschäftigunsverhältnis zurückgibt
      2. Parametrisieren der Algorithmen über den Typ des Beschäftigunsverhältnisses
    • Heraus kam folgender Code - reduziert auf die Applikation - die die Strategien ( Arten des Beschäftigunsverhältnisses ) nutzt

    danach

    import strategySecond.Employment;
    import strategySecond.Boss;

    import strategySecond.Manager;
    import strategySecond.Officer;
    import strategySecond.Employee;

    import strategySecond.Worker;

    public class strategySecond {
    public static void main(String[] args) {

    if ( args.length != 1){
    System.out.println("Usage: java strategyFirst number ");

    System.exit(0);
    }
    Employment employ= getEmployment( Integer.parseInt(args[0]));

    System.out.println("Annual Vacation: " + getAnnualVacation( employ));
    System.out.println("Bonnification: "+ getBonification( employ ) );

    System.out.println("WeeklyWorkingHourse: "+ getWeeklyWorkingHours(employ ));
    System.out.println("Loan: " + getLoan( employ ) );

    }

    private static Employment getEmployment( int type ){

    switch ( type ){
    // boss
    case 0:

    return new Boss();
    // manager
    case 1:

    return new Manager();
    //officer
    case 2:
    return new Officer();

    //employee
    case 3:
    return new Employee();
    // rest

    default:
    return new Worker();
    }
    }


    private static double getBonification( Employment employ ){
    //something to do

    return employ.getBonification();
    }

    private static int getWeeklyWorkingHours( Employment employ ){
    //something to do

    return employ.getWeeklyWorkingHours();
    }

    private static double getAnnualVacation( Employment employ ){

    //something to do
    return employ.getAnnualVacation();
    }

    private static double getLoan( Employment employ ){

    //something to do
    return employ.getLoan();
    }
    }
    • die verschiedenen Strategien kann man einfacher mit einem Klassendiagramm darstellen
    • Beschäftigunsverhältnisse:
      Beschäftigunsverhältnisse

    Klassische Sicht

    • da neben dem Strategie Pattern bei dem obigen Code nach anderen Pattern zur Verwirrung beigetragen haben, will ich das Strategie Pattern noch in seiner klassischen Form beschreiben

    Anwendbarkeit

    • viele verwandte Klassen unterscheiden sich nur in ihrem Verhalten
    • unterschiedliche Verhaltensweisen eines Alogrithmus sollen unterstützt werden

    Lösung

    • Kapsle die unterschiedlichen Verhaltensweisen in einem Objekt, mit dem du die Methode/Klasse parametrisieren kannst

    klassisches Beispiel

    • Sort Algorithmus:
      Strategy Pattern

    Konsequenzen

    • Alternative zur Unterklassenbildung, insbesondere zur Template Methode
    • das Allheilmittel gegen bedingtes Programmieren
    • der Klient kann zu Laufzeit ( Unterschied zur Template Methode ) seine Strategie wechseln
    • jede Strategie verlangt einen neuen Strategiehierachie

    Variante

    • das Stratgiepattern wird auch gern Policy oder Traits bezeichnet

    Traits

    • Insbesondere Traits betonen aber nicht das Parametrisieren von Algorithmen, sondern das Parametrisiern von Typen.
    • Die Strukturmittel um diese Varianz im Verhalten zu erreichen sind die oben beschriebenen Strukturmittel, nur die Intention ist eine andere.
    • Bekannte Beispiele sind die Container der STL in C++ aber auch insbesondere:

    std::string und std::wstring

    typedef basic_string<char> string;

    typedef basic_string<wchar> wstring;
    mit
    template<class Ch, class Tr= char_traits<Ch>, class A= allocator<Ch> > 

    class std::basic_string{
    ...
    };
    • std::string ist ein typedef auf basic_string, der mit dem Charakter char parametrisiert wurde
    • dieser Charaktertype char gibt die Defaultwerte für seine Kernfunktionalität als Zeichen und seine Allokierungsstrategie vor
    • implementiert man für seinen Charakteryp char_traits und allocator<>MyChar>, so spricht nicht gegen den neuen, eventuell lokalisierten String
    typedef basic_string<Mychar> MyString;
    • gerade durch das generische Programmieren mittels Templates ist es möglich, die Varianz des Verhaltens schon zur Compilezeit zu bestimmen, so daß zur Laufzeit kein virtueller Dispatch mehr notwendig ist

     


  • Techniken in C und C++

    Motivation

    Dieses Seite enthält viele Technikvergleiche von C und C++. Die Idee ist es, typischen C-Programmiereraufgaben ihre C++-Lösungen gegenüber zu stellen. Dabei verwende ich C-Code, der nicht state of the artist. C-Code, den ich bei meiner täglichen Arbeit sehe. Diesem stelle ich state of the artC++-Code gegenüber. Dieser Technikvergleich ist natürlich nicht objektiv, denn meine persönliche Sicht trifft das Zitat von Bjarne Stroustrup sehr gut:C++ is "a better C" in the sense that it supports the styles of programming done using C with better type checking and more notational support...(http://www.stroustrup.com/bs_faq.html#difference).
    Natürlich lassen sich auch fast alle C-Lösungen in C++ einsetzen. Die Vergleiche sind bewusst schematisch aufgebaut um die Unterschiede schnell auf den Punkt zu bringen.

    Makros

    • C++ ist eine 3-Stufen Sprache
    • jede Stufe verwendet die Ergebnisse der vorherigen Stufen
    1. Präprozessor
      • einfache Textsubstitution ohne Semantik
    2. Templates
      • Instanziierung der Templates zur Compilezeit ist eine funktionale, turing-complete Subsprache in C++
      • das Ergebnis der Instanziierung ist C++-Sourcecode
    3. konventionelles C++

    Konstanten

    Zweck

    • werden zur Compilezeit ausgewertet
    • können als Größe eines Arrays verwendet werden
    • werden im ROM gespeichert

    C

    #define MAX 14

    C++

    const int MAX= 14;

    Vergleich

    SpracheSemantikSyntax
    C reine Textersetzung keine Syntax
    C++ Sprachkonstrukt normale C++-Syntax

    Anmerkung

    • durch das Schlüsselwortconstexprlassen sich Konstanten erklären, die zur Compilezeit evaluiert werden
    constexpr int MAX= 14;

    Funktionen

    Zweck

    • werden vor der Laufzeit evaluiert
    • ersetzen ihren Funktionsaufruf mit ihrem Funktionskörper
    • bieten größeres Optimierungspotential für den Compiler

    C

    #include <stdio.h>
     
    #define max(i, j) (((i) > (j)) ? (i) : (j))
     
    int main(void){
     
      int i= max(10,11);
      printf("%d\n",i);                   
      printf("%d\n",max(100,-10));        
      printf("%d\n",max("nonsense",3));   
      printf("%f\n",max("3",3));          
     
      return 0;
     
    }
    • ergibt
    11
    100
    4195968
    0.000000
    

    C++

    #include <iostream>
     
    template<typename T>
    T max (T i, T j){
      return ((i > j) ? i : j);
    }
     
    int main(){
     
      std::cout << max(10,11) << std::endl;            // 11
      std::cout << max(100,-10) << std::endl;          // 100
      std::cout << max(10,1.5) << std::endl;           // ERROR
      // std::cout << max("nonsense",3) << std::endl;  // ERROR
      // std::cout << max("3",3) << std::endl;         // ERROR
     
    }
    • das Übersetzen von max(10,1.5) führt zum Fehler, da 10 vom Typ int und 1.5 vom Typ double ist:
    C++/makroCpp.cpp: In function 'int main()':
    C++/makroCpp.cpp:13:26: error: no matching function for call to 'max(int, double)'
    C++/makroCpp.cpp:13:26: note: candidate is:
    C++/makroCpp.cpp:4:3: note: template<class T> T max(T, T)
    C++/makroCpp.cpp:4:3: note:   template argument deduction/substitution failed:
    C++/makroCpp.cpp:13:26: note:   deduced conflicting types for parameter 'T' ('int' and 'double')
    

    Vergleich

    SpracheSemantikSyntax
    C reine Textersetzung keine Syntax
    C++ Instanziierung zur Compilezeit Template Syntax

    Anmerkung

    • neben Funktions-Templates bieten sich inline-Funktionen als Ersatz für Makros an
    • durch das Schlüsselconstexprlassen sich Funktionen als Compilezeit-Funktionen deklarieren
    constexpr int max (int i,int j){
      return ((i > j) ? i : j);
    }

    Parametrisierte Container

    Zweck

    • sind sehr wichtige Datenstrukturen
    • bieten eine deutliche Vereinfachung im Umgang mit sequentiellen und assoziativen Datenstrukturen an
    • bieten ein ähnliches Interface an
    • erlauben es dem Programmierer, sich auf die Verarbeitung der Daten zu konzentrieren

    C

    #ifndef KVEC_H
    #define KVEC_H
     
    #include <stdint.h>
    #include <stdio.h>
    #include <stdlib.h>
     
    #define VEC_t(type) struct { uint32_t n, m; type *a; }
    #define VEC_init(v) ((v).n = (v).m = 0, (v).a = 0)
    #define VEC_destroy(v) free((v).a)
    #define VEC_A(v, i) ((v).a[(i)])
    #define VEC_size(v) ((v).n)
    #define VEC_max(v) ((v).m)
     
    #define VEC_resize(type, v, s)  ((v).m = (s), (v).a = (type*)realloc((v).a, sizeof(type) * (v).m))
     
    #define VEC_push(type, v, x) do {                                    \
            if ((v).n == (v).m) {                                        \
                (v).m = (v).m? (v).m<<1 : 2;                             \
                (v).a = (type*)realloc((v).a, sizeof(type) * (v).m);     \
            }                                                            \
            (v).a[(v).n++] = (x);                                        \
        } while (0)
     
    #endif
     
    typedef VEC_t(int) MyIntVec;
     
    int main(void){
     
      MyIntVec myIntVec;
      VEC_init(myIntVec);
      VEC_resize(int,myIntVec,10);
      printf("size: %d\n",VEC_size(myIntVec));              
      printf("max: %d\n",VEC_max(myIntVec));               
      VEC_push(int,myIntVec,0);
      VEC_push(int,myIntVec,1);
      VEC_push(int,myIntVec,2);
      VEC_push(int,myIntVec,3);
      VEC_push(int,myIntVec,4);
      printf("size: %d\n",VEC_size(myIntVec));              
      printf("max: %d\n",VEC_max(myIntVec));                
      printf("myIntVec[3]: %d\n",VEC_A(myIntVec,3));        
      VEC_destroy(myIntVec);
     
    }
    • ergibt
    size: 0
    max: 0
    size: 6
    max: 10
    myIntVec[3]: 3
    

    C++

    #include <iostream>
    #include <vector>
     
    int main(){
     
      std::vector<int> myIntVec;
      std::cout << "capacity: " << myIntVec.capacity() << std::endl;   
      std::cout << "size: " << myIntVec.size() << std::endl;           
      myIntVec={0,1,2,3,4,5};
      std::cout << "capacity: " << myIntVec.capacity() << std::endl;   
      std::cout << "size: " << myIntVec.size() << std::endl;           
      std::cout << "myIntVec[3]: " << myIntVec[3] << std::endl;        
     
    }
    • ergibt
    capacity: 0
    size: 0
    capacity: 11
    size= 6
    myIntVec[3]: 3
    

    Vergleich

    SpracheSemantikSyntaxErweiterungFehleranfälligkeitSpeichermanagement
    C reine Textersetzung keine Syntax Entwickler hoch Entwickler
    C++ Sprachkonstrukt normale C++-Syntax Umfang des C++-Standards keine automatisch

    Anmerkung

    • die C++ Variante bietet ein deutlich mächtigeres Interface an:[1]
    • die C++-Variante verwaltet den dynamischen Speicher automatisch
    • C++ besitzt viele sequentielle und assoziative Container:[2]
    • die Anforderungen an Container steigen
      • Thread-Safe
      • verschiedene Speicherallokationsstrategien
      • Lock-Free

    Konstanten

    Zeiger

    Nullzeiger
    Zweck
    • zeigen an, dass auf nichts verwiesen wird
    • kennzeichnen einer Leerstelle
    C
    #include <stdio.h>
     
    int main(void){
     
      int* pi= 0;
      int i= 0;
     
      int* pj= NULL;
      int j= NULL;
     
    }
    C++
    int main(){
     
      int* pi= nullptr;
      // int i= nullptr;      // ERROR
      bool b= nullptr;
     
    }
    Vergleich
    Spracheimplizite Konvertierung nach intimplizite Konvertierung nach 0 oder NullzeigerKonvertierung nach bool
    C ja ja ja
    C++ nein nein ja
    Anmerkung
    • C++: das implizite konvertieren eins Nullzeigers (nullptr) in eine natürliche Zahl ist eine häufige Ursache von Fehlern ⇒nullptrlässt sich nicht nachintkonvertieren
    • C: die Nullzeiger Konstante (NULL) lässt sich nachchar*und nachintkonvertieren ⇒ der Aufruf der FunktionoverloadTest(NULL)ist nicht eindeutig
    std::string overloadTest(char*){
      return "char*";
    }
    std::string overloadTest(int){
      return "int";
    }
     
    overloadTest(NULL); // ERROR
    • das Beispiel geht davon aus, dasNULLals0Loder0LLdefiniert ist
    • wirdNULLals0definiert, ist der AufrufoverloadTest(NULL)ist eindeutig

    Typen

    Bool

    Zweck

    • Datentyp für die Wahrheitswertetrueoderfalse

    C

    typedef int bool;
     
    #define true 1
    #define false 0
     
    enum { false, true };                  
    enum bool { false, true };

    C++

    bool a= true;
    bool b= false

    Vergleich

    Sprachebuilt-in Datentypimplizite Konvertierung nach 0/1
    C nein ja
    C++ ja ja

    Anmerkung

    • der Vektor ist für den Datentypboolauf Speicheranforderung optimiert:std::vector<bool>

    String

    • ist eine Sequenz von Zeichen, beendet durch ein Null-Zeichen

    Zweck

    • elementarer Datentyp für Worte und Sätze

    C

    #include <stdio.h>
    #include <string.h>
     
    int main( void ){
     
      char text[10];
     
      strcpy(text, "The Text is too long for text.");   // undefined behavior because text is to big
      printf("strlen(text): %u\n", strlen(text));      // undefined behavior because text has no termination character '\0'
      printf("%s\n", text);
     
      text[sizeof(text)-1] = '\0';
      printf("strlen(text): %u\n", strlen(text));
     
      return 0;
    }
    • ergibt
    strlen(text): 29
    The Text is to long for text.
    strlen(text): 9
    Speicherzugriffsfehler
    
    • obwohl das Programm undefiniertes Verhalten besitzt, lässt es sich ohne Warnung übersetzen

    C++

    #include <iostream>
    #include <string>
     
    int main(){
     
      std::string text{"The Text is not to long for text."};   // automatic storage allocation and string termination
     
      std::cout << "text.size(): " << text.size() << std::endl;
      std::cout << text << std::endl;
     
      text +=" And can still grow!";
     
      std::cout << "text.size(): " << text.size() << std::endl;
      std::cout << text << std::endl;
     
    }
    • ergibt
    text.size(): 33
    The Text is not to long for text.
    text.size(): 53
    The Text is not to long for text. And can still grow!
    

    Vergleich

    SpracheSpeichermanagementDynamische GrößenanpassungString Termination ZeichenFehleranfälligkeit
    C explizite Allokation und Deallokation explizit durch den Programmierer explizit durch den Programmierer hoch
    C++ automatisch Allokation und Deallokation automatisches reallokieren automatisch niedrig

    Anmerkung

    • der Umgang mit Strings ist neben dem Umgang mit Speicher die häufigste Fehlerquelle in C-Code
    C
    • die String-Buchhaltung muss vom Programmierer explizit vollzogen werden
      • Allokation
      • Deallokation
      • String Terminations Zeichen
      • Länge des Strings bestimmen
    C++
    • das String-Buchhaltung wird vom Compiler automatisch vollzogen
    • C++-Strings bieten ein reiches Interface an
      • die String-Methoden[3]
      • die Algorithmen der Standard Template Library[4]
      • die Reguläre-Ausdrücke-Bibliothek[5]

    Initialisierung

    Zweck

    • Variablen sollen vor ihrer Verwendung initialisiert werden
    • konstante Variablen müssen vor ihrer Verwendung initialisiert werden

    C

    #include <stdio.h>
    #include <stdint.h>
     
    int main(void){
     
      double dou= 3.14159;
      int a= dou;               // NO ERROR or WARNING
      int8_t smallInt= 2011;    // WARNING
     
      printf("\ndou: %f\n",dou);
      printf("a: %f\n",a);
      printf("smallInt: %i\n",smallInt);
     
      if ( a == 3 ) printf("\na == 3\n");
     
      return 0;
    }
    • ergibt
    dou: 3.141590
    a: 3.141590
    smallInt: -37
    
    a == 3
    

    C++

    • in C++ lässt sich alles mit geschweiften ( {} ) Klammern initialisieren
    • bei der Initialisierung mit geschweiften Klammern ( {} ) findet kein Verengung des Datentyps statt
    #include <iostream>
     
    int main(){
     
      double dou= 3.14159;
      // int c= {dou};         // ERROR
      // int d{dou};           // ERROR
     
      // int8_t f= {2011};     // ERROR
      int8_t g= {14};
     
      std::cout << "g: " << static_cast<int>(g) << std::endl;
     
    }
    • ergibt
    g: 14
    

    Vergleich

    SpracheInitialisierungVerengung
    C mit runden ( () ) Klammern ja
    C++ mit geschweiften ( {} ) Klammern nein

    Anmerkung

    • vergessene Initialisierung von Variablen führt zu undefiniertem Programmverhalten
    • eine Variable soll bei ihrer Definition initialisiert werden

    Konvertierung

    Zweck

    • Zuweisungen an einen anderen Datentypen
    • Konvertierung zu und vonvoid*
    • dem Compiler den Datentyp vorschreiben

    C-Cast

    #include <iostream>
     
    int main(){
      for (int x= 0; x < 128; ++x) {
        std::cout << x <<": "<< (char)x << std::endl;
      } 
    }

    C++-Casts

    • drücken ihr Anliegen direkt aus
    const_cast
    • erlaubt die Qualifierconstodervolatilevom Argument zu entfernen oder hinzuzufügen
    double myDouble=5.5;
    const double* myDoublePointC= &myDouble;
    double* myDoubleP= const_cast<double*>(myDoublePointC);
    static_cast
    • wird zur Compilezeit ausgeführt
    • erlaubt zwischen verwandten Typen zu konvertieren
      • Konvertierungen zwischen Zeigertypen in derselben Klassenhierarchie
      • Aufzählungen in eine Ganzzahl
      • Ganzzahl in eine Gleitkommazahl
    double myDouble= 5.5;
    int i= static_cast<int>(myDouble);
    reinterpret_cast
    • erlaubt
      • einen Zeiger in einen beliebigen anderen Zeiger
      • einen beliebigen Zeiger in einen beliebigen integralen Typ zu konvertieren und anders herum
    double* myDouble= new double();
    void* myVoid= reinterpret_cast<void*>(myDouble);
    double* myDouble1= reinterpret_cast<double*>(myVoid);
    dynamic_cast
    • prüft zur Laufzeit, ob die Konvertierung möglich ist
    • konvertiert einen Zeiger(Referenz) eines Klassentyps in ein anderen Zeiger(Referenz) in der gleichen Ableitungskette
    • kann nur auf polymorphe Typen angewandt werden
    • ist Bestandteil derrun-time type information(RTTI)
    • ist die Konvertierung nicht möglich, gibt diese einen Nullzeiger (nullptr) bei Zeigern, eine Ausnahme bei Referenzen zurück
    class A{
    public:
      virtual ~A();
    };
     
    class B : public A{
    public:
      void onlyB(){};
    };
     
    void funWithRef(A& myA){
      try{
        B& myB = dynamic_cast<B&>(myA);
        myB.onlyB();
      }
      catch (const std::bad_cast& e){
        std::cerr << e.what() << std::endl;
        std::cerr << "No B" << std::endl;
      }
    }
     
    void funWithPointer(A* myA){
      B* myB = dynamic_cast<B*>(myA);
      if (myB != nullptr) myB->onlyB();
      else std::cerr << "No B" << std::endl;
    }

    Vergleich

    Spracheeindeutige Konvertierungexplizite Syntax
    C nein nein
    C++ ja ja

    Anmerkung

    • C-Konvertierungen
      • sind eine Kombination von verschiedenen C++-Casts→ const_cast → static_cast → reinterpret_cast
      • sind schwierig im Code zu identifizieren

    Anweisungen

    for-Anweisung

    Zweck

    • wird verwendet, um die Elemente eines Containers zu bearbeiten

    C

    int i; 
    for(i=0; i<10; ++i){}
    printf(i);

    C++

    for (int i= 0; i<10;++i){}
    std::cout << i << std::endl; // ERROR

    Vergleich

    SpracheDefinition der LaufzeitvariableSichtbarkeit der Laufzeitvariable
    C vor der for-Anweisung im umgebenden Bereich der for-Anweisung
    C++ in der for-Anweisung im Bereich der for-Anweisung

    Anmerkung

    • meist lässt sich eine explizite for-Anweisung durch einen Algorithmus der Standard Template Library ersetzen
    std::vector<int> myVev={1,2,3,4,5,6,7,8,9};
    std::copy(myVec.begin(), myVec.end(), std::ostream_iterator<int>(std::cout, " "));
    • eine Range-basierte For-Schleife ist deutlich kompakter als eine for-Anweisung
    std::vector<int> myVev={1,2,3,4,5,6,7,8,9};
    for ( int i: myVec ) std::cout << i << " ";

    case-Anweisung

    Zweck

    • durch case-Anweisungen lässt sich der Programmfluss explizit steuern
    • sind ein einfaches Mittel Zustandsautomaten zu implementieren

    C

    #include <stdio.h>
     
    typedef enum Tag_Dispatch{
      IMPL_A,
      IMPL_B,
      IMPL_C
    } Impl_Tag;
     
    const char* getNameA(){
      return "Implementation A";
    }
     
    const char* getNameB(){
      return "Implementation B";
    }
     
    void displayName(Impl_Tag tag){
        switch(tag){
            case IMPL_A:
                printf("%s\n",getNameA());
                break;
            case IMPL_B:
                printf("%s\n",getNameB());
                break;
        }
    }
     
    void displayNameWithDefault(Impl_Tag tag){
        switch(tag){
            case IMPL_A:
                printf("%s\n",getNameA());
                break;
            case IMPL_B:
                printf("%s\n",getNameB());
                break;
            default:
                printf("No Implementation\n");
        }
    }
     
    int main(void){
     
      displayName(IMPL_A);
      displayName(IMPL_B);
      displayName(IMPL_C);
     
      printf("\n");
     
      displayNameWithDefault(IMPL_A);
      displayNameWithDefault(IMPL_B);
      displayNameWithDefault(IMPL_C);
     
      return 0;
    }
    • ergibt
    Implementation A
    Implementation B
    
    Implementation A
    Implementation B
    No Name
    

    C++

    #include <iostream>
    #include <string>
     
    class Interface{
    public:
      virtual std::string getName() const = 0;
      virtual std::string getNameWithDefault() const{
        return "No Implementation";
      }
    };
     
    class ImplementationA : public Interface{
      std::string getName() const {
        return "Implementation A";
      }
      std::string getNameWithDefault() const{
        return "Implementation A";
      }
    };
     
    class ImplementationB : public Interface{
      std::string getName() const {
        return "Implementation B";
      }
      std::string getNameWithDefault() const{
         return "Implementation B";
      }
    };
     
    class ImplementationC : public Interface{
      std::string getName() const {
         return "Implementation C";
       }
    };
     
    void showMyName(const Interface& a){
       std::cout << a.getName() << std::endl;
    }
     
    void showMyNameWithDefault(const Interface& a){
       std::cout << a.getNameWithDefault() << std::endl;
    }
     
    int main(){
     
      const Interface& impA= ImplementationA();
      const Interface& impB= ImplementationB();
     
      showMyName(impA);
      showMyName(impB);
     
      std::cout << std::endl;
     
      const Interface& impC= ImplementationC();
     
      showMyNameWithDefault(impA);
      showMyNameWithDefault(impB);
      showMyNameWithDefault(impC);
     
    }
    • ergibt
    Implementation A
    Implementation B
    
    Implementation A
    Implementation B
    No Name
    

    Vergleich

    SpracheProgrammlogikErweiterbarkeitProgrammfluss
    C implementiert jede Funktion jede case-Anweisung muss überarbeitet werden bestimmt explizit der Programmierer
    C++ definiert Klassenstruktur Klassenstruktur wird erweitert bestimmt implizit der Compiler

    Anmerkung

    • sind eine Verallgemeinerungen der bedingten (if/else) Anweisungen
    • case-Anweisung gelten als schlechter Programmstil, da sie
      • sehr viel Pflegeaufwand benötigen
      • sehr fehleranfällig sind
      • die Kontrolllogik vervielfältigen

    Funktionen

    Overloading

    Zweck

    • eine Funktion soll mit verschiedenen Typen aufgerufen werden

    C

    #include <stdio.h>
     
    void print_Int(int i){
      printf("%d\n",i);
    }
     
    void print_Char(const char* c){
      printf("%s\n",c);
    }
     
    int main(void){
     
      print_Int(2011);
      print_Char("Hello world");
    }

    C++

    • in C++ können Funktionen mit dem selben Namen definiert werden, die sich nur in den Typen der Parameter unterscheiden
    #include <iostream>
    #include <string>
     
    void print(int i){
      std::cout << i << std::endl;
    }
     
    void print(const std::string& str){
      std::cout << str << std::endl;
    }
     
    int main(){
      print(2011);
      print("Hello World");
    }

    Anmerkung

    main-Funktion

    Zweck

    • die main-Funktion ist der Startpunkt des Programms

    C

    int main( void ){
      . . . 
      return 0;
    }

    C++

    int main(){
      . . .
    }

    Vergleich

    SpracheArgumentreturn-Anweisung
    C voidbei keinem Argument notwendig notwendig
    C++ kein Argument notwendig vom Compiler wird automatischreturn 0;</code erzeugt

    Anmerkung

    • C-Funktionen benötigen immer ein Argument; <code>void steht für die Abwesenheit eines Arguments
    • die Kommandozeilenargumente lassen sich in C und C++ durch die erweiterte main-Funktionmain(int argc, char* argv[])einlesen
      • argc enthält die Anzahl der Argumente
      • argv enhält ein Array von C-Strings; argv[0] ist der Name des Programms

    Funktionszeiger, Funktoren und Lambda-Funktionen

    Zweck

    • Algorithmen lassen sich über Zeiger auf Funktionen, Funktionsobjekte und Lambda-Funktionen parametrisieren
    • je mehr Einsicht der Compiler in den Code hat, desto besser kann er optimieren
    • Lambda-Funktionen besitzen des höchste Optimierungspotential, gefolgt von Funktionsobjekten und Funktionszeiger

    C

    • Parametrisierung über einen Funktionszeiger
    #include <algorithm>
    #include <iomanip>
    #include <iostream>
    #include <vector>
     
    void add3(int* i){
      *i +=3;
    }
     
    int main(){
      std::cout << std::endl;
     
      std::vector<int> myVec1{1,2,3,4,5,6,7,8,9,10};
      std::cout << std::setw(20) << std::left << "myVec1: i->i+3:     ";
      std::for_each(myVec1.begin(),myVec1.end(),&add3);
      for (auto v: myVec1) std::cout << std::setw(6) << std::left << v;
     
      std::cout << "\n\n";
    }
    • ergibt
    myVec1: i->i+3:     4     5     6     7     8     9     10    11    12    13 
    

    C++

    Funktionsobjekte
    • werden durch das Überladen des Klammeroperators zum Funktionsobjekt
    • verhalten sich wie Funktionen mit Zustand
    • lassen sich über den Konstruktor parametrisieren
    #include <algorithm>
    #include <iomanip>
    #include <iostream>
    #include <vector>
     
    class AddN{
    public:
      AddN(int n):num(n){};
      void operator()(int& i){
        i +=num;
      }
    private:
      int num;
    };
     
    int main(){
      std::cout << std::endl;
     
      std::vector<int> myVec2{1,2,3,4,5,6,7,8,9,10};
      AddN add4(4);
      std::for_each(myVec2.begin(),myVec2.end(),add4);
      std::cout << std::setw(20) << std::left << "myVec2: i->i+4:     ";
      for (auto v: myVec2) std::cout << std::setw(6) << std::left << v;
      std::cout << "\n";
     
      std::vector<int> myVec3{1,2,3,4,5,6,7,8,9,10};
      AddN addMinus5(-5);
      std::for_each(myVec3.begin(),myVec3.end(),addMinus5);
      std::cout << std::setw(20) << std::left << "myVec3: i->i-5:     ";
      for (auto v: myVec3) std::cout << std::setw(6) << std::left << v;
     
      std::cout << "\n\n";
    }
    • ergibt
    myVec2: i->i+4:     5     6     7     8     9     10    11    12    13    14    
    myVec3: i->i-5:     -4    -3    -2    -1    0     1     2     3     4     5  
    
    Lambda-Funktionen
    • erlauben den Code an Ort und Stelle zu definieren
    #include <algorithm>
    #include <cmath>
    #include <iomanip>
    #include <iostream>
    #include <vector>
     
    int main(){
      std::cout << std::endl;
     
      std::vector<int> myVec4{1,2,3,4,5,6,7,8,9,10};
      std::cout << std::setw(20) << std::left << "myVec4: i->3*i+5:   ";
      std::for_each(myVec4.begin(),myVec4.end(),[](int& i){i=3*i+5;});
      for (auto v: myVec4) std::cout << std::setw(6) << std::left << v;
      std::cout << "\n";
     
      std::vector<int> myVec5{1,2,3,4,5,6,7,8,9,10};
      std::cout << std::setw(20) << std::left << "myVec5: i->i*i   ";
      std::for_each(myVec5.begin(),myVec5.end(),[](int& i){i=i*i;});
      for (auto v: myVec5) std::cout << std::setw(6) << std::left << v;
      std::cout << "\n";
     
      std::vector<double> myVec6{1,2,3,4,5,6,7,8,9,10};
      std::for_each(myVec6.begin(),myVec6.end(),[](double& i){i=std::sqrt(i);});
      std::cout << std::setw(20) << std::left << "myVec6: i->sqrt(i): ";
      for (auto v: myVec6) std::cout << std::fixed << std::setprecision(2) << std::setw(6) << v ;
     
      std::cout << "\n\n";
    }
    • ergibt
    myVec4: i->3*i+5:   8     11    14    17    20    23    26    29    32    35    
    myVec5: i->i*i      1     4     9     16    25    36    49    64    81    100   
    myVec6: i->sqrt(i): 1.00  1.41  1.73  2.00  2.24  2.45  2.65  2.83  3.00  3.16 
    

    Vergleich

    SpracheCodelokalitätRückgabetyp
    C sehr gering bei Funktionszeiger muss explizit angegeben werden
    C++ sehr hoch bei Lambda-Funktionen wird vom Compiler bei Lambda-Funktionen automatisch bestimmt

    Anmerkung

    • der Compiler erzeugt aus der Lambda-Funktion implizit ein Funktionsobjekte und instanziiert dies
    • Lambda-Funktionen sollten
      • kurz und knackig sein
      • selbsterklärend sein
    • da Funktoren und Lambda-Funktionen direkt an der Stelle ihre Verwendung instanziiert werden, ist ihre Verwendung in der Regel performanter als die von Funktionszeigern

    Klassen

    Methoden

    Virtuelle Methoden

    Zweck
    • virtuelle Methoden sind an Objekte gebunden Methoden, die sich abhängig vom Typ des Objektes verhalten
    C
    #include <stdio.h>
    #include <stdlib.h>
     
    typedef enum {Circle, Triangle} ShapeKind;
     
    typedef struct {
      ShapeKind sKind;
    } Shape;
     
    // constructor
    Shape* createNewShape(ShapeKind s) {
      Shape* p = (Shape*) malloc(sizeof(Shape));
      p->sKind = s;
      return p;
    }
     
    // virtual dispatch
    void draw(Shape* s){
      switch(s->sKind){
      case Circle:
        printf("draw a Circle\n");
      break;
      case Triangle:
        printf("draw a Triangle\n");
        break;
      }
    };
     
    int main(void){
      Shape* shapeCirc= createNewShape(Circle);
      Shape* shapeTriang= createNewShape(Triangle);
      draw(shapeCirc);     
      draw(shapeTriang);    
      return 0;
    }
    • ergibt
    draw a Circle
    draw a Triangle
    
    • die AufzählungShapeKinddient als Diskriminator
    • jede StrukturShapeerhält eine spezielleShapeKind
    • die FunktioncreateNewShapegibt neue Objekte zurück, die ihr richtigeShapeKindbesitzen
    • in der Funktiondrawfindet der virtuelle Dispatch aufgrund derShapeKindstatt
    C++
    #include <iostream>
     
    class Shape{
    public:
      virtual void draw()= 0;
      virtual ~Shape()= default;
    };
     
    class ShapeCircle: public Shape{
    public:
      void draw(){
        std::cout << "draw a circle" << std::endl;
      }
    };
     
    class ShapeTriangle: public Shape{
    public:
      void draw(){
        std::cout << "draw a triangle" << std::endl;
      }
    };
     
    int main(){
      Shape* myShapeCir= new ShapeCircle;
      Shape* myShapeTriang= new ShapeTriangle;
     
      myShapeCir->draw();     
      myShapeTriang->draw(); 
    }
    • ergibt
    draw a circle
    draw a triangle
    
    • die abstrakte BasisklasseShapegibt das Interface für alle Shape-Klassen vor
    • jede konkrete KlasseShapeCircleoderShapeTrianglemuss die Methodedrawimplementieren
    Vergleich
    SpracheKapselungErweiterbarkeitProgrammfluss
    C nein Diskriminiator und jede case-Struktur muss angepasst werden explizit durch den Programmierer
    C++ ja Erzeugung einer neuen Klasse implizit durch den Compiler
    Anmerkung
    C
    • simuliert nur virtuelle Methoden
    C++
    • ermöglicht deutlich komplexere Varianten
      • Mehrfachvererbung
      • virtuelle, nicht virtuelle oder rein virtuelle Methoden

    Variablen

    Kapseln von Variablen in Strukturen

    Zweck
    • um den globalen Namensraum nicht zu verschmutzen, werden Variablen in Strukturen gepackt
    C
    #include <stdio.h>
     
    typedef struct{
      int a;
    } Test;
     
    int main(void){
      Test test;
      test.a= 5;
      printf("%d\n",test.a);
      return 0;
    }
    • ergibt
    5
    
    C++
    #include <iostream>
     
    class Test{
    public:
      int getA() const { return a; }   // implicit inline
      inline void setA(int);
    private:
      int a;
    };
     
    void Test::setA(int a_){
      a=a_;
    }
     
    int main(){
      Test test;
      test.setA(5);
      std::cout << test.getA() << std::endl;
    }
    • ergibt
    5
    
    Vergleich
    SpracheVerbositätKapselungUnterscheidung Schreib- Lesezugriff
    C gering nur durch den Struktur-Qualifier nein
    C++ hoch vollständig ja
    Anmerkung
    • die C++-Varianten ist so effizient wie die C-Variante, da die Methode explizitinlineerklärt werden kann oder implizitinlineist
    • die Kapselung einer Variable in einer Struktur in C trägt nur dazu bei, dass der globale Namensraum durch diese nicht verschmutzt wird

    Speicher

    Allokation

    Zweck

    • Speicher wird dann erst allokiert, wenn er benötigt werden
    • Datenstrukturen fordern zur Laufzeit Speicher nach

    C

    Monitor* monC= (Monitor*)malloc(sizeof(Monitor));
    if (monC == 0) error("memory exhausted");
    monC->init(2);

    C++

    new
    Monitor* monCpp= new Monitor(2);
    Smart Pointer
    explizite Besitzverhältnisse
    std::unique_ptr<Monitor> uniquePtr(new Monitor(2));
    auto uniquePtr2= std::make_unique<Monitor>(2);      // C++14
    geteilte Besitzverhältnisse
    std::shared_ptr<Monitor> shardPtr(new Monitor(2));
    std::shared_ptr<Monitor> sharedPtr2= sharedPtr;
    auto sharedPtr3= std::make_shared<Monitor>(2);

    Vergleich

    SpracheInitialisierung des Objektsautomatische Verwaltung des Lebenszyklus der Daten
    C nein nein
    C++ ja Smart Pointer

    Anmerkung

    • die Smart Pointerunique_ptrundshared_ptrverwalten automatisch die ihnen anvertraute Ressource
    shared_ptr
    • bietet Reference Counting an
    • besitzt eine Referenz auf seine Ressource und eine auf den gemeinsamen Zähler
      • inkrementiert seinen Referenzzähler, wenn er kopiert wird
      • dekrementiert seinen Referenzzähler, wenn er gelöscht wird
      • wenn der Referenzzähler den Wert 0 erreicht, gibt es sich und seine Ressource sofort frei
    unique_ptr
    • verwaltet den Lebenszyklus einer Ressource
    • besitzt die gleiche Performance wie ein native Pointer an
    • wenn er seine Gültigkeit verliert, gibt es sich und seine Ressource sofort frei

    Deallokation

    Zweck

    • Speicher muss zeitnah freigegeben werden um wieder zur Verfügung zu stehen

    C

    free monC;

    C++

    delete monCpp;

    Vergleich

    Spracheautomatischer Freigabe des Objektesautomatische Verwaltung des Lebenszyklus der Ressource
    C nein nein
    C++ nein bei native Zeigern mittels Smart Pointer

    Anmerkung

    • der C-Ausdruck gibt den Speicher frei
    • der C++-Ausdruck gibt den Speicher frei und ruft den Destruktor des Objektes auf
    • Speicher soll mit Smart Pointern verwaltet werden

    Bibliothek

    Array

    Zweck

    • Arrays die sequentiellen Datenstrukturen in C

    C

    #include <stdio.h>
     
    int main( void ){
      int myArray[]={1,2,3,4,5,6};
      int sum;
      int i;
      for( i = 0; i < 6; i++ ){
        sum += myArray[i];
      } 
      printf("Sum: %d \n", sum );     
      return 0;
    }
    • ergibt
    Sum: 21
    

    C++

    #include <array>
    #include <iostream>
    #include <numeric>
     
    int main(){
      std::array<int,6> myArray= {1,2,3,4,5,6};
      std::cout << "Sum: " << std::accumulate(myArray.begin(),myArry.end(),0) << std::endl;   
    }
    • ergibt
    Sum: 21
    

    Vergleich

    SpracheStatische GrößeSpeicheranforderungKompatibel mit der Standard Template Library
    C ja minimal nein
    C++ ja minimal ja

    Anmerkung

    • std::arrayvereint das Beste aus zwei Welten
      • es besitzt eine statische Größe und minimale Speicheranforderungen wie das C-Array
      • es besitzt ein Interface wie derstd::vector
    #include <algorithm>
    #include <array>
    #include <iostream>
    #include <iterator>
     
    const int NUM= 10;
     
    int main(){
     
      std::cout << std::boolalpha;
     
      std::array<int,NUM> arr{{0,1,2,3,4,5,6,7,8,9}};
      std::cout << std::endl << "arr: ";
      for ( auto a: arr){
        std::cout << a << " " ;
      }
     
      std::cout << std::endl;
     
      // initializer list
      std::array<int,NUM> arr2{{19,11,14,18,14,15,16,12,17,13}};
      std::cout << std::endl << "arr2 unsorted: ";
      std::copy(arr2.begin(),arr2.end(), std::ostream_iterator<int>(std::cout, " "));
      std::sort(arr2.begin(),arr2.end());
      std::cout << std::endl << "arr2 sorted: ";
      std::copy(arr2.rbegin(),arr2.rend(), std::ostream_iterator<int>(std::cout, " "));
     
      std::cout << std::endl;
     
      // get the sum and the mean of arr2
      double sum= std::accumulate(arr2.begin(),arr2.end(),0);
      std::cout << "sum of a2: " << sum << std::endl;
      double mean= sum / arr2.size();
      std::cout << "mean of a2: " << mean << std::endl;
     
     
      // swap arrays
      std::swap(arr,arr2);
      std::cout << std::endl << "arr2: ";
        for ( auto a: arr){
          std::cout << a << " " ;
        }
     
      std::cout << std::endl;
     
      // comparison
      std::cout << "(arr < arr2): " << (arr < arr2 ) << std::endl;
     
      auto count= std::count_if(arr.begin(),arr.end(),[](int i){ return (i<15) or (i>18) ; });
      std::cout << "Numbers smaller then 15 or bigger then 18: " << count << std::endl;
     
      std::cout << std::endl;
     
    }
    • ergibt
    arr: 0 1 2 3 4 5 6 7 8 9 
    
    arr2 unsorted: 19 11 14 18 14 15 16 12 17 13 
    arr2 sorted: 19 18 17 16 15 14 14 13 12 11 
    sum of a2: 149
    mean of a2: 14.9
    
    arr2: 11 12 13 14 14 15 16 17 18 19 
    (arr < arr2): false
    Numbers smaller then 15 or bigger then 18: 6
    
    

    Techniken

    Ausgabe

    Zweck

    • formatierte Ausgabe von verschiedenen Datentypen auf der Konsole

    C

    #include <stdio.h>
     
    int main(){
     
      printf("Characters: %c %c \n", 'a', 65);
      printf("Decimals: %d %ld\n", 2011, 650000L);
      printf("Preceding with blanks: %10d \n", 2011);
      printf("Preceding with zeros: %010d \n", 2011);
      printf("Doubles: %4.2f %+.0e %E \n", 3.1416, 3.1416, 3.1416);
      printf("%s \n", "From C to C++");
      return 0;
    }
    • ergibt
    Characters: a A 
    Decimals: 2011 650000
    Preceding with blanks:       2011 
    Preceding with zeros: 0000002011 
    Doubles: 3.14 3.141600e+00
    From C to C++ 
    

    C++

    #include <iomanip>
    #include <iostream>
     
    int main(){
     
      std::cout << "Characters: " << 'a' << " " <<  static_cast<char>(65) << std::endl;  
      std::cout << "Decimals: " << 2011 << " " << 650000L << std::endl;
      std::cout << "Preceding with blanks: " << std::setw(10) << 2011 << std::endl;
      std::cout << "Preceding with zeros: " << std::setfill('0') << std::setw(10) << 20011 << std::endl;
      std::cout << "Doubles: " << std::setprecision(3) << 3.1416 << " " << std::setprecision(6) << std::scientific <<  3.1416 << std::endl;
      std::cout << "From C to C++" << std::endl;
     
    }
    • ergibt
    Characters: a A
    Decimals: 2011 650000
    Preceding with blanks:       2011
    Preceding with zeros: 0000020011
    Doubles: 3.14 3.141600e+00
    From C to C++
    

    Vergleich

    SpracheTypisierungTypsicherheit
    C explizit durch den Programmierer nein
    C++ implizit durch den Compiler ja

    Anmerkung

    C
    • falsche Formatspezifier führen zu undefinierten Verhalten
    printf("%d\n",2011);             // 2011
    printf("%d\n",3.1416);           // 2147483643
    printf("%d\n","2011");           // 4196257
    printf("%s\n",2011);             // Speicherzugriffsfehler
    C++
    std::cout << 2011 << std::endl;     // 2011
    std::cout << 3.1416 << std::endl;   // 3.1416
    std::cout << "2011" << std::endl;   // "2011"
    #include <iostream>
    #include <stdexcept>
    #include <string>
     
    void printf_(const char *s){
      while (*s) {
        if (*s == '%' && *(++s) != '%')
          throw std::runtime_error("invalid format string: missing arguments");
          std::cout << *s++;
      }
    }
     
    template<typename T, typename... Args>
    void printf_(const char *s, T value, Args... args){
      while (*s) {
        if (*s == '%' && *(++s) != '%') {
          std::cout << value;
          ++s;
          printf_(s, args...); // call even when *s == 0 to detect extra arguments
          return;
        }
        std::cout << *s++;
      }
      throw std::logic_error("extra arguments provided to printf");
    }
     
    int main() {
      std::cout << std::endl;
     
      const char* m = "The value of %s is about %g.\n";
      printf_(m,"pi", 3.14159);
      printf(m,"pi", 3.14159);
     
      // printf_("A string: %s");      // std::runtime_error
                                       // what(): invalid format string: missing argument
      printf("A string: %s");       
     
      std::cout << std::endl;
     
    }
    • ergibt beim Compilieren
    g++ -std=c++11 -Wall -g -c -o obj/printf.o C++/printf.cpp
    C++/printf.cpp: In function ‘int main()’:
    C++/printf.cpp:39:24: warning: format ‘%s’ expects a matching ‘char*’ argument [-Wformat]
    g++ -std=c++11 -Wall -g -o bin/printf obj/printf.o
    
    • ergibt bei der Ausführung
    The value of pi is about 3.14159.
    The value of pi is about 3.14159.
    A string: A string: of pi is about 3.14159ut
    
    • C-printf
      • besitzt undefiniertes Verhalte
    • C++-printf_
      • führt beim Compilieren zu einer Warnung
      • führt zu einem Laufzeitfehler

    Lebenszyklus einer Ressource

    Zweck

    • Typischer Weise besteht der Lebenszyklus einer Ressource aus den 3 Abschnitten
      1. Initialisierung der Ressource
      2. Arbeiten mit der Ressource
      3. Freigeben der Ressource

    C

    #include <stdio.h>
     
    void initDevice(const char* mess){
      printf("\n\nINIT: %s\n",mess);
    }
     
    void work(const char* mess){
      printf("WORKING: %s",mess);
    }
     
    void shutDownDevice(const char* mess){
      printf("\nSHUT DOWN: %s\n\n",mess);
    }
     
    int main(void){
     
      initDevice("DEVICE 1");
      work("DEVICE1");
      {
        initDevice("DEVICE 2");
        work("DEVICE2");
        shutDownDevice("DEVICE 2");
      }
      work("DEVICE 1");
      shutDownDevice("DEVICE 1");
     
      return 0;
     
    }
    • ergibt
    INIT: DEVICE 1
    WORKING: DEVICE1
    
    INIT: DEVICE 2
    WORKING: DEVICE2
    SHUT DOWN: DEVICE 2
    
    WORKING: DEVICE 1
    SHUT DOWN: DEVICE 1
    

    C++

    • RAIIsteht für für das sehr häufig verwendete C++-IdiomResourceAcquisitionIsInitialization
    • dabei wird die Ressource im Konstruktor gebunden und im Destruktor wieder freigegeben
    #include <iostream>
    #include <string>
     
    class Device{
      private:
        const std::string resource;
      public:
        Device(const std::string& res):resource(res){
          std::cout << "\nINIT: " << resource << ".\n";
        }
        void work() const {
          std::cout << "WORKING: " << resource << std::endl;
        }
        ~Device(){
          std::cout << "SHUT DOWN: "<< resource << ".\n\n";
        }
    };
     
    int main(){
     
     
      Device resGuard1{"DEVICE 1"};
      resGuard1.work();
     
      {
        Device resGuard2{"DEVICE 2"};
        resGuard2.work();
      }
      resGuard1.work();
     
    }
    • der Lebenszyklus der Ressource
    INIT: DEVICE 1.
    WORKING: DEVICE 1
    
    INIT: DEVICE 2.
    WORKING: DEVICE 2
    SHUT DOWN: DEVICE 2.
    
    WORKING: DEVICE 1
    SHUT DOWN: DEVICE 1.
    

    Vergleich

    SpracheAutomatisches InitialisierenAutomatisches FreigebenRessource-Löcher
    C nein nein ja (Nachlässigkeit des Programmierers, Ausnahmen)
    C++ ja (Konstruktor) ja (Destruktor) nein

    Anmerkung

    • RAII ist ein sehr beliebtes Idiom in C++ für
      • Speicher: die Smart Pointer verwalten ihren Speicher
      • Mutexe: die Locks verwalten ihre Mutexe

    Globale Variablen und Funktionen

    Zweck

    • Variablen und Funktionen definieren, die global zur Verfügung stehen

    C

    int showNumber= 0;
     
    void increaseNumberShocks(void){
      ++shockNumber;
    }

    C++

    class Shock{
    static int number;
    public:
      static void increaseNumber();   
    };
     
    int Shock::number= 0;
    void Shock::increaseNumberShocks(){
      ++numberShock;
    }
    • statische Attribute und Methoden einer Klassen
      • müssen außerhalb der Klasse definiert werden
      • sind an die Klasse, nicht an die Objekte gebunden
      • können ohne Objekt aufgerufen werden
      • werden über den Klassenraum gekapselt

    Vergleich

    SpracheStatische VariableStatische Methode
    C nein nein
    C++ ja ja

    Anmerkung

    • statische Attribute und Methoden einer Klasse
      • eignen sich gut um globale Variablen und Funktionen zu ersetzen
      • verschmutzen nicht den globalen Namensraum
    • die Variablenumberdes Objektesshockist an das Objekt gebunden ⇒ ein zweites Objekt vom TypShockkann instanziiert werden, das die gleiche statische Variablenumbernutzt
    • Namensräume sind ein weiteres Mittel um den globalen Namensraum frei zu halten
    namespace Shock{
      int number= 0;
    }
    Shock::number++;

    Zeiger versus Referenzen

    Zweck

    • Variablen werden oft indirekt über ihre Adresse (Zeiger) oder einen alternativen Namen (Referenz) angesprochen
    • diese Indirektion erlaubt weitere Anwendungsfälle

    C

    #include <stdio.h>
     
    void swap(int* x, int* y){
     
      int tmp = *x;
      *x = *y;
      *y = tmp;
     
    }
     
    int main( void ){
     
      int i= 2011;
      int* iptr;
      iptr= &i;    // set iptr to the adress of i
      int j;
      int k= 2014;
     
      j= *iptr;         // set j to the value of i
      printf("iptr: %p \n",iptr);
      printf("*iptr: %i \n", *iptr);
      printf("j: %i\n",j);
      printf("k: %i\n",k);
     
      printf("\n");
     
      *iptr= k;         // set i to the value of k
      printf("iptr: %p \n",iptr);
      printf("*iptr: %i \n", *iptr);
      printf("j: %i\n",j);
      printf("k: %i\n",k);
     
      printf("\n");
     
      swap(&j,&k);
     
      printf("j: %i\n",j);
      printf("k: %i\n",k);
     
      return 0;
    }
    • ergibt
    iptr: 0x7fff080e7734 
    *iptr: 2011 
    j: 2011
    k: 2014
    
    iptr: 0x7fff080e7734 
    *iptr: 2014 
    j: 2011
    k: 2014
    
    j: 2014
    k: 2011
    

    C++

    #include <iostream>
     
    void swap(int& x,int& y){
     
      int tmp = x;
      x = y;
      y = tmp;
     
    }
     
    int main(){
     
      int i= 2011;
      int& refi= i;    // refi is a alias of i
      std::cout << "i: "  << i << std::endl;
      std::cout << "refi: "<< refi << std::endl;
     
      std::cout << std::endl;
     
      refi= 2014;
      std::cout << "i: "  << i << std::endl;
      std::cout << "refi: "<< refi << std::endl;
     
      std::cout << std::endl;
     
      int j= 2011;
      int k= 2014;
      std::cout << "j: "  << j << std::endl;
      std::cout << "k: " << k << std::endl;
     
      swap(j,k);
      std::cout << std::endl;
     
      std::cout << "j: "  << j << std::endl;
      std::cout << "k: " << k << std::endl;
     
    }
    • ergibt
    i: 2011
    refi: 2011
    
    i: 2014
    refi: 2014
    
    j: 2011
    k: 2014
    
    j: 2014
    k: 2011
    

    Vergleich

    SpracheInitialisierungBindungsdauerSemantik
    C Deklaration und Initialisierung können getrennt sein Zeiger können auf neue Adressen verweisen Unterscheidung zwischen dem Wert (*iptr) und der Adresse des Zeigers (iptr) ist notwendig
    C++ muss bei der Definition erfolgen eine Referenz ist immer an das gleiche Objekt gebunden verhalten sich wie Variablen

    Anmerkung

    • Zeiger und Referenzen sind die Grundlage für Polymorphie in der objektorientierten Programmierung in C++
    Zeiger
    • unterstützen Zeigerarithmetik
     
    int p[10] = {0,1,2,3,4,5,6,7,8,9};
    int* point= p;
    printf("%d\n", *point);       // 0
    point++;
    printf("%d\n", *point);       // 1
     
    point +=8;
    printf("%d\n", *point);       // 9
     
    point--;
    printf("%d\n", *point);       // 8
    • vom Typ void können auf Daten beliebigen Typs verweisen
    double d= 3.17;
    void* p= &d;
    • können auf Funktionen verweisen
    void addOne(int& x){
      x+= 1;
    }
    void (*inc)(int& x)= addOne;
    Referenzen
    • verhalten sich wie konstante Zeiger
    • verweisen ihre ganze Lebenszeit auf das gleiche Objekt

Seite 1 von 2

Mentoring

Stay Informed about my Mentoring

 

Rezensionen

Tutorial

Besucher

Heute 737

Gestern 3357

Woche 12300

Monat 39617

Insgesamt 3892331

Aktuell sind 26 Gäste und keine Mitglieder online

Kubik-Rubik Joomla! Extensions

Abonniere den Newsletter (+ pdf Päckchen)

Beiträge-Archiv

Sourcecode

Neuste Kommentare