2012-11-11 3 views
12

Wie implementiere ich meine eigenen benutzerdefinierten Stream-Manipulator, so dass es klebrig ist. Zum Beispiel möchte ich ganze Zahlen konvertieren, um binären, so dass:Sticky benutzerdefinierte Stream-Manipulator

cout << "decimal of 4: " << 4 
    << "\ndecimal of 4: " << 4 
    << binary << "\nbinary of 4: " << 4 
    << "\nbinary of 4: " << 4 
    << nobinary << "\ndecimal of 4: " << 4 
    << "\ndecimal of 4: " << 4 << endl; 

zurückkehren würde:

decimal of 4: 4 
decimal of 4: 4 
binary of 4: 100 
binary of 4: 100 
decimal of 4: 4 
decimal of 4: 4 
+3

Ich würde den 'nobinary' Begriff töten und sie' std :: dec' usw. verwenden lassen. – chris

+1

Möchten Sie, dass es auch nach dem Ende der Aussage bleibt? –

+2

BTW, [nicht verwenden Sie 'std :: endl'] (http://kuhllib.com/2012/01/14/stop-excessive-use-of-stdendl/). –

Antwort

16

die ganzen Dinge zu tun, ist ein bisschen beteiligt. Um es verständlich zu machen, fange ich mit den grundlegenden Dingen an: Benutzerdefinierte Formatierungsflags für benutzerdefinierte Typen verwenden. Die benutzerdefinierte Formatierung von Ganzzahlen folgt unten.

Die Iostream Klassen ableiten [indirekt] aus std::ios_base die zwei Speicher für Daten zur Verfügung stellt: std::ios_base::iword() und std::ios_base::pword() für int s und void* sind. Die Beibehaltung des zugewiesenen Speichers, der mit std::ios_base::pword() gespeichert wurde, ist nicht trivial und wird glücklicherweise für diesen relativ einfachen Anwendungsfall nicht benötigt. Um diese Funktion zu verwenden, die beide eine nicht-const Referenz auf den entsprechenden Typ zurückgeben, weisen Sie normalerweise einen Index unter Verwendung std::ios_base::xalloc() einmal in Ihrem Programm zu und verwenden es wann immer Sie auf Ihre benutzerdefinierten Formatierungsflags zugreifen müssen. Wenn Sie auf einen Wert mit iword() oder pword() zugreifen, wird initial Null initialisiert. Um die Dinge zusammen, hier ist ein kleines Programm demonstriert dies:

#include <iostream> 

static int const index = std::ios_base::xalloc(); 

std::ostream& custom(std::ostream& stream) { 
    stream.iword(index) = 1; 
    return stream; 
} 

std::ostream& nocustom(std::ostream& stream) { 
    stream.iword(index) = 0; 
    return stream; 
} 

struct mytype {}; 
std::ostream& operator<< (std::ostream& out, mytype const&) { 
    return out << "custom-flag=" << out.iword(index); 
} 

int main() 
{ 
    std::cout << mytype() << '\n'; 
    std::cout << custom; 
    std::cout << mytype() << '\n'; 
    std::cout << nocustom; 
    std::cout << mytype() << '\n'; 
} 

Nun wird ein int wie 4 ist kein benutzer definiert Typ, und es gibt bereits einen Ausgabeoperator für diese definiert. Glücklicherweise können Sie die Art und Weise, in der Ganzzahlen mithilfe von Facetten formatiert werden, genauer anpassen, indem Sie verwenden. Nun, dies zu tun, müssen Sie eine Reihe von Schritten tun:

  1. Leiten Sie eine Klasse von std::num_put<char> und überschreiben die do_put() Mitglieder, die Sie zu spezialisierten Verhalten geben wollen.
  2. Erstellen Sie ein std::locale Objekt mit der neu erstellten Facette.
  3. std::ios_base::imbue() der Stream mit dem neuen std::locale.

Um die Dinge schöner für den Benutzer zu machen, möchten Sie vielleicht ein neues std::locale mit einer geeigneten std::num_put<char> Facette heraufbeschwören, wenn der Manipulator verwendet wird. Die mit der Erstellung einer geeigneten Facette jedoch bevor dies zu tun, lassen Sie beginnen:

#include <bitset> 
#include <iostream> 
#include <limits> 
#include <locale> 

static int const index = std::ios_base::xalloc(); 

class num_put 
    : public std::num_put<char> 
{ 
protected: 
    iter_type do_put(iter_type to, 
        std::ios_base& fmt, 
        char_type fill, 
        long v) const 
    { 
     if (!fmt.iword(index)) { 
      return std::num_put<char>::do_put(to, fmt, fill, v); 
     } 
     else { 
      std::bitset<std::numeric_limits<long>::digits> bits(v); 
      size_t i(bits.size()); 
      while (1u < i && !bits[i - 1]) { 
       --i; 
      } 
      for (; 0u < i; --i, ++to) { 
       *to = bits[i - 1]? '1': '0'; 
      } 
      return to; 
     } 
    } 
#if 0 
    // These might need to be added, too: 
    iter_type do_put(iter_type, std::ios_base&, char_type, 
        long long) const; 
    iter_type do_put(iter_type, std::ios_base&, char_type, 
        unsigned long) const; 
    iter_type do_put(iter_type, std::ios_base&, char_type, 
        unsigned long long) const; 
#endif 
}; 

std::ostream& custom(std::ostream& stream) { 
    stream.iword(index) = 1; 
    return stream; 
} 

std::ostream& nocustom(std::ostream& stream) { 
    stream.iword(index) = 0; 
    return stream; 
} 

int main() 
{ 
    std::locale loc(std::locale(), new num_put); 
    std::cout.imbue(loc); 
    std::cout << 13 << '\n'; 
    std::cout << custom; 
    std::cout << 13 << '\n'; 
    std::cout << nocustom; 
    std::cout << 13 << '\n'; 
} 

Was ein bisschen hässlich ist, ist, dass es notwendig zu imbue() der Brauch std::locale die custom Manipulator zu verwenden. Um werde diese los zu werden, können wir nur sicherstellen, dass die individuelle Facette in den verwendeten std::locale installiert ist, und, wenn dies nicht der Fall, nur um es zu installieren, wenn das Flag gesetzt:

std::ostream& custom(std::ostream& stream) { 
    if (!stream.iword(index) 
     && 0 == dynamic_cast<num_put const*>(
       &std::use_facet<std::num_put<char> >(stream.getloc()))) { 
     stream.imbue(std::locale(stream.getloc(), new num_put)); 
    } 
    stream.iword(index) = 1; 
    return stream; 
} 

Was nun bleibt, ist auch überschreiben die verschiedenen do_put() Mitglieder ordnungsgemäß mit den verschiedenen unsigned Typen und mit long long arbeiten, aber das ist als Übung übrig.

+0

Das Ding, das ich nicht bekam, war der dynamic_cast. Wofür ist das? – 0x499602D2

+0

@ 0x499602D2: Alle 'dynamic_cast' überprüft, ob eine benutzerdefinierte' std :: locale' '' imbue() 'ed sein muss: Wenn bereits eine geeignete Facette installiert ist, ist es nicht nötig,' imbue() ' eine neue Facette. Betrachtet man die Bearbeitung, scheint es tatsächlich so zu sein, dass die frühere Bearbeitung dem Code den zuvor korrekten Code verlieh! Der 'dynamic_cast' hat versucht auf das benutzerdefinierte Facet' num_put' zu testen, nicht auf 'std :: num_put '! –

+0

Entschuldigung. Ich habe eine falsche Bearbeitung vorgenommen. lol – 0x499602D2