Donnerstag, 10. Mai 2018

RAII beim Verwenden der stream- Neuzuordnungen

Weitere GUI- Elemente als streambuf und RAII zum aufräumen

Kurz vor dem Feiertag ein weitere Post. Wie bereits angekündigt, möchte ich einfach weitere Steuerelemente zur Ausgabe verwenden, zum anderen ist bisher immer noch das manuelle Aufräumen notwendig. In dem Post RAII - Mauszeiger für VCL + FMX habe ich ja erklärt, dass dieses dank RAII nicht notwendig ist, und durch C++ selber verhindert werden kann. Also möchte ich in diesem Post beginnen, einen entsprechenden RAII- Wrapper entwerfen. Weitere Steuerelemente werde ich im nächsten Post betrachten.


Bei unserem RAII- Wrapper soll über die Spezialisierung einer template- Methode Activate() das Erzeugen der konkreten Hilfsklassen und die Zuordnung übernommen werden, und die notwendige temporäre Variable als privates Element gespeichert werden. Dieses wird benutzt, um beim Verlassen des Gültigkeitsbereich der bisherige Zustand wiederhergestellt werden. 

Daraus ergibt sich folgende Klasse.

class TMyStreamWrapper {
   private:
     std::ostream&   str;
     std::streambuf* old;
   public:
     TMyStreamWrapper(std::ostream& ref) : str(ref) { old = 0; }
     ~TMyStreamWrapper(void) {
       if(old != 0) delete str.rdbuf(old);
       }

     void Reset(void) {
        if(old != 0) {
           delete str.rdbuf(old);
           old = 0;
           }
        }

     template <typename ty> void Activate(ty* element);

     operator std::ostream& (void) { return str; };

   private: // pre C++11
     TMyStreamWrapper(TMyStreamWrapper const& ref) : str(ref.str) { };

     void Check(void) { if(old != 0) throw std::runtime_error("Attention, prior activation"); }
   };

template<>
inline void TMyStreamWrapper::Activate<std::streambuf>(std::streambuf* buff) {
   Check();
   old = str.rdbuf(buff);
   }

#if defined BUILD_WITH_VCL || defined BUILD_WITH_FMX
template<>
inline void TMyStreamWrapper::Activate<TMemo>(TMemo* element) {
   Check();
   old = str.rdbuf(new MyMemoStreamBuf(element));
   }

#endif

template<typename ty>
inline void TMyStreamWrapper::Activate(ty* element) {
   throw std::runtime_error("no specification for TMyStreamWrapper::Activate");
   return;
   }


Schauen wir uns die Klasse "TMyStreamWrapper" genauer an. Wir definieren zwei Datenelemente. Das erste ist eine Referenz auf einen ostream "str", damit auch auf jede davon abgeleitete Klasse, und repräsentiert den umzulenkenden Stream. Als zweites definieren wir einen Zeiger "old" auf das vorherige streambuf- Element des Streams, das zwischengespeichert wird. Im Konstruktor übergeben wir eine Referenz auf einen ostream als Parameter.  Dieser soll neu zugeordnet werden. Da es sich um eine Referenz handelt, muss die Zuweisung zu dem Datenelement "str" in der Kostruktorenliste erfolgen, also vor der öffnenden geschweiften Klammer des Funktionsblocks. Das zweite Datenelement "old" wird mit 0 initialisiert. Ich habe mich hier zur Kompatibilität mit C++98 entschieden, sollten Sie nur einen aktuellen Compiler verwenden, kann und sollte dieses natürlich nullptr sein. Oder sie verwenden die bedingte Compilierung um zwischen der Zuweisung von 0 und nullptr zu unterscheiden.

Im Destruktor wird das bisherige, zwischengespeicherte Datenelement "old" genutzt, um den vorherigen Zustand wiederherzustellen. Dazu wird geprüft, ob das Datenelement ungleich 0 ist,
und dann wird der vorherige Zustand durch den Aufruf von rdbuf() wiederhergestellt und das übergebene Datenelement gelöscht.

Die Methode Reset() setzt den bisherigen Zustand zurück und löscht das vorher übergebene streambuf- Objekt. Dadurch ist es möglich, eine neue Zuordnung vorzunehmen.

Das template Activate() übernimmt diese Zuordnung und bildet den eigentlich wichtigsten Teil damit. Es gibt später die Spezialisierungen, die als explizite inline Methoden unterhalb der Klasse implementiert sind. Spezialisierungen erweitern das Konzept der templates erheblich und verschaffen uns viele neue Möglichkeiten. Aber es ist erstaunlich, das viele diese nicht nutzen. 

Wichtig ist dabei die Methode, die einen Zeiger auf ein streambuf- Objekt übernimmt, und damit jedes beliebige von streambuf abgeleitete Objekte übernehmen kann. Es ist ein universeller Zugang zu diesem Wrapper. Den Abschluss bildet die allgemeine template Definition (ohne Spezialisierung), die aber einen Laufzeitfehler auslöst. Zwischen diesen beiden kann später alles ergänzt werden, im obigen Listing ist es die von uns schon durchgeführte Umlenkung auf ein Memofeld mit Hilfe der Klasse MyMemoStreamBuf aus dem Post Verwendung von Standardstreams zur Ausgabe in Memofeldern.

Der Funktionsoperator wird genutzt, um den umgeleiteten Stream von außen auch benutzen zu können. Deshalb wird hier einfach eine Referenz auf ostream zurückgeliefert, mit dem Datenelement "str".

Die Check- Methode wird genutzt, um bei der Aktivierung eine mehrfache Zuweisung zu verhindern. Sollte das Datenelement "old" ungleich 0 sein, wird hier ein Laufzeitfehler ausgelöst.

Als letztes der Kopierkonstrukor. Mit C++11 könnte ich diesen einfach löschen, weil ich hier aber kompatible zu älteren Compilern bleiben wollte, habe ich mich für den bekannten Trick entschieden, zu verhindern das von außen eine Kopie der Instanz initiiert wird. Deshalb wird der Kopierkonstruktor einfach in den privaten Teil verschoben, so dass nur noch die Methoden der Klasse selber Kopien erzeugen könnten. Wieder gilt hier, sollten sie nur einen aktuellen Compiler verwenden, löschen sie den Kopierkonstruktor bitte.

Nun können wir diesen Wrapper verwenden, um die Zuordnung am Programmende rückgängig zu machen und sicherzustellen, dass es zu keinen Fehlern kommt. 
;