2009-04-28 3 views
48

In C++ ändert, eine Zahl in Hexadezimal drucken tun Sie dies:C++ benutzerdefinierte Stream Manipulator, der nächste Punkt auf Strom

int num = 10; 
std::cout << std::hex << num; // => 'a' 

Ich weiß, dass ich einen Manipulator schaffen, das wie so einfach Sachen in den Stream ergänzt:

Wie kann ich jedoch einen Manipulator erstellen, der, wie "hex", Elemente ändert, die in den Stream kommen sollen? Als ein einfaches Beispiel, wie würde ich schaffen die plusone Manipulator hier ?:

int num2 = 1; 
std::cout << "1 + 1 = " << plusone << num2; // => "1 + 1 = 2" 

// note that the value stored in num2 does not change, just its display above. 
std::cout << num2; // => "1" 

Antwort

62

definieren. Zuerst müssen Sie einige Zustand in jedem Strom speichern.

inline int geti() { 
    static int i = ios_base::xalloc(); 
    return i; 
} 

ostream& add_one(ostream& os) { os.iword(geti()) = 1; return os; } 
ostream& add_none(ostream& os) { os.iword(geti()) = 0; return os; } 

Nachdem das fertig ist, kann man schon abrufen einige Zustand in allen Strömen: Sie können durch xalloc gegeben mit der Funktion iword und einem Index, den Sie es passieren, zu tun. Jetzt müssen Sie nur noch in den jeweiligen Ausgabevorgang einhaken. Die numerische Ausgabe erfolgt über eine Facette, da sie möglicherweise abhängig vom Gebietsschema ist.So können Sie tun

struct my_num_put : num_put<char> { 
    iter_type 
    do_put(iter_type s, ios_base& f, char_type fill, long v) const { 
     return num_put<char>::do_put(s, f, fill, v + f.iword(geti())); 
    } 

    iter_type 
    do_put(iter_type s, ios_base& f, char_type fill, unsigned long v) const { 
     return num_put<char>::do_put(s, f, fill, v + f.iword(geti())); 
    } 
}; 

Jetzt können Sie das Zeug testen.

int main() { 
    // outputs: 11121011 
    cout.imbue(locale(locale(),new my_num_put)); 
    cout << add_one << 10 << 11 
     << add_none << 10 << 11; 
} 

Wenn Sie möchten, dass nur die nächste Zahl erhöht wird, setzen Sie einfach das Wort zu 0 wieder nach jedem Aufruf do_put.

+2

+1. Dies ist die gründliche, nach dem Buch (und wie Sie sehen, ziemlich komplexe) Lösung. Aber ich frage mich, ob es nicht einfacher (und möglicherweise klarer) wäre, einfach eine Funktion plusone() zu erstellen, die ein Argument nimmt und das Ergebnis zurückgibt? –

+1

sehr schöne Ausstellung! –

+0

Danke Leute :) –

-1

Die hex, dec und oct Manipulatoren einfach die streambasefield Eigenschaft des vorhandenen ändern.

Weitere Informationen zu diesen Manipulatoren finden Sie unter C++ Reference.

Wie in Neil Butterworth's answer veröffentlicht, müssten Sie die vorhandenen Stream-Klassen erweitern oder eigene erstellen, um Manipulatoren zu erhalten, die zukünftige Werte beeinflussen, die in den Stream eingefügt werden.

In dem Beispiel Ihres plusone Manipulators müsste das Stream-Objekt ein internes Flag haben, um anzuzeigen, dass eines zu allen eingefügten Werten hinzugefügt werden soll. Der plusone Manipulator würde einfach dieses Flag setzen, und der Code zum Behandeln des Stream-Einfügens würde dieses Flag prüfen, bevor Zahlen eingefügt werden.

+1

haben leider -1 - iostreams einen Erweiterungsmechanismus enthalten (xalloc(), iword(), pword()) wie von litb. –

+2

Keine Notwendigkeit, sich zu entschuldigen. Ich lag eindeutig falsch, und ich hätte es nicht gewusst, ohne die -1 zu sehen. Danke, dass Sie mich darauf aufmerksam gemacht haben! –

11

Ich stimme völlig mit Neil Butterworth auf diesem, aber in dem speziellen Fall, den Sie verwenden, können Sie diesen total schrecklichen Hack tun. Tun Sie dies in keinem Produktionscode. Es hat viele Bugs. Zum einen funktioniert es nur in Ihrem One-Liner oben, es verändert nicht den Zustand des zugrundeliegenden Streams.

class plusone_stream : public std::ostream 
{ 
    public: 
    std::ostream operator<<(int i) 
    { 
     _out << i+1; 
     return *this; 
    } 
}; 

std::ostream& plusone(std::ostream& out) 
{ 
    return plusone_stream(out); 
} 
+0

Sie schlagen mich dazu :) –

1

habe ich eine einfache Lösung für Ihren Testfall ohne <iomanip> zu verwenden. Ich kann nicht versprechen, dass der gleiche Ansatz im wirklichen Leben funktioniert.

Der grundlegende Ansatz ist, dass cout << plusone ein temporäres Hilfsobjekt (PlusOnePlus) zurückgibt, das seinerseits die überladene operator << hat, die die Addition durchführt.

ich es unter Windows getestet haben:

PlusOne plusone; 
cout << plusone << 41 

produziert "42", wie erwartet. Hier ist der Code:

class PlusOnePlus { 
public: 
    PlusOnePlus(ostream& os) : m_os(os) {} 
    // NOTE: This implementation relies on the default copy ctor, 
    // assignment, etc. 
private: 
    friend ostream& operator << (PlusOnePlus& p, int n); 
    ostream& m_os; 
}; 

class PlusOne { 
public: 
    static void test(ostream& os); 
}; 

PlusOnePlus operator << (ostream& os, const PlusOne p) 
{ 
    return PlusOnePlus(os); 
} 

ostream& operator << (PlusOnePlus& p, int n) 
{ 
    return p.m_os << n + 1; 
} 

void PlusOne::test(ostream& os) 
{ 
    PlusOne plusone; 
    os << plusone << 0 << endl; 
    os << plusone << 41 << endl; 
} 

EDIT: kommentiert der Code darauf hinweisen, dass ich auf dem Standard-Copykonstruktor verlasse mich (etc.) für PlusOnePlus. Eine robuste Implementierung würde wahrscheinlich diese

+0

Clever, aber beachten Sie, dass das folgende nicht funktioniert: "os << plusone; os << 41;" –

+0

Ich könnte argumentieren, dass das ein Feature ist :-), obwohl es zugegebenermaßen inkonsistent mit dem Standard ist, wie Manipulatoren funktionieren. –

+1

Ja, ich bezweifle, dass jemand eine weitere unnötige Inkonsistenz in C++ bemerken wird ...;) +1. –

1

Sie müssen mit Streamstates spielen. Ich habe die folgenden Links zu diesem Thema vorgemerkt:

Als Maciej Sobczak Bibliothek ist nicht mehr online verfügbar, und als Lizenz erlaubt mir, dies zu tun, (richtig mich wenn ich falsch liege), hier ist eine Kopie seiner Haupt-Datei, die ich habe es geschafft, aus der Vergessenheit zu retten:

// streamstate.h 
// 
// Copyright (C) Maciej Sobczak, 2002, 2003 
// 
// 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. 
// 
// <http://lists.boost.org/Archives/boost/2002/10/38275.php> 
// <http://www.ddj.com/dept/cpp/184402062?pgno=1> 
// <http://www.msobczak.com/prog/publications.html> 

#ifndef STREAMSTATE_H_INCLUDED 
#define STREAMSTATE_H_INCLUDED 

#include <ios> 
#include <istream> 
#include <ostream> 

// helper exception class, thrown when the source of error 
// was in one of the functions managing the additional state storage 
class StreamStateException : public std::ios_base::failure 
{ 
public: 
    explicit StreamStateException() 
     : std::ios_base::failure(
      "Error while managing additional IOStream state.") 
    { 
    } 
}; 

// State should be: 
// default-constructible 
// copy-constructible 
// assignable 

// note: the "void *" slot is used for storing the actual value 
//  the "long" slot is used to propagate the error flag 
template 
< 
    class State, 
    class charT = char, 
    class traits = std::char_traits<charT> 
> 
class streamstate 
{ 
public: 
    // construct with the default state value 
    streamstate() {} 

    // construct with the given stream value 
    streamstate(const State &s) : state_(s) {} 

    // modifies the stream 
    std::basic_ios<charT, traits> & 
    modify(std::basic_ios<charT, traits> &ios) const 
    { 
     long *errslot; 
     void *&p = state_slot(ios, errslot); 

     // propagate the error flag to the real stream state 
     if (*errslot == std::ios_base::badbit) 
     { 
      ios.setstate(std::ios_base::badbit); 
      *errslot = 0; 
     } 

     // here, do-nothing-in-case-of-error semantics 
     if (ios.bad()) 
      return ios; 

     if (p == NULL) 
     { 
      // copy existing state object if this is new slot 
      p = new State(state_); 
      ios.register_callback(state_callback, 0); 
     } 
     else 
      *static_cast<State*>(p) = state_; 

     return ios; 
    } 

    // gets the current (possibly default) state from the slot 
    static State & get(std::basic_ios<charT, traits> &ios) 
    { 
     long *errslot; 
     void *&p = state_slot(ios, errslot); 

     // propagate the error flag to the real stream state 
     if (*errslot == std::ios_base::badbit) 
     { 
      ios.setstate(std::ios_base::badbit); 
      *errslot = 0; 
     } 

     // this function returns a reference and therefore 
     // the only sensible error reporting is via exception 
     if (ios.bad()) 
      throw StreamStateException(); 

     if (p == NULL) 
     { 
      // create default state if this is new slot 
      p = new State; 
      ios.register_callback(state_callback, 0); 
     } 

     return *static_cast<State*>(p); 
    } 

private: 
    // manages the destruction and format copying 
    // (in the latter case performs deep copy of the state) 
    static void state_callback(std::ios_base::event e, 
     std::ios_base &ios, int) 
    { 
     long *errslot; 
     if (e == std::ios_base::erase_event) 
     { 
      // safe delete if state_slot fails 
      delete static_cast<State*>(state_slot(ios, errslot)); 
     } 
     else if (e == std::ios_base::copyfmt_event) 
     { 
      void *& p = state_slot(ios, errslot); 
      State *old = static_cast<State*>(p); 

      // Standard forbids any exceptions from callbacks 
      try 
      { 
       // in-place deep copy 
       p = new State(*old); 
    } 
      catch (...) 
      { 
       // clean the value slot and 
       // set the error flag in the error slot 
       p = NULL; 
       *errslot = std::ios_base::badbit; 
      } 
     } 
    } 

    // returns the references to associated slot 
    static void *& state_slot(std::ios_base &ios, long *&errslot) 
    { 
     static int index = std::ios_base::xalloc(); 
     void *&p = ios.pword(index); 
     errslot = &(ios.iword(index)); 

     // note: if pword failed, 
     // then p is a valid void *& initialized to 0 
     // (27.4.2.5/5) 

     return p; 
    } 

    State state_; 
}; 

// partial specialization for iword functionality 
template 
< 
    class charT, 
    class traits 
> 
class streamstate<long, charT, traits> 
{ 
public: 
    // construct with the default state value 
    streamstate() {} 

    // construct with the given stream value 
    streamstate(long s) : state_(s) {} 

    // modifies the stream 
    // the return value is not really useful, 
    // it has to be downcasted to the expected stream type 
    std::basic_ios<charT, traits> & 
    modify(std::basic_ios<charT, traits> &ios) const 
    { 
     long &s = state_slot(ios); 
     s = state_; 

     return ios; 
    } 

    static long & get(std::basic_ios<charT, traits> &ios) 
    { 
     return state_slot(ios); 
    } 

private: 
    static long & state_slot(std::basic_ios<charT, traits> &ios) 
    { 
     static int index = std::ios_base::xalloc(); 
     long &s = ios.iword(index); 

     // this function returns a reference and we decide 
     // to report errors via exceptions 
     if (ios.bad()) 
      throw StreamStateException(); 

     return s; 
    } 

    long state_; 
}; 

// convenience inserter for ostream classes 
template 
< 
    class State, 
    class charT, 
    class traits 
> 
std::basic_ostream<charT, traits> & 
operator<<(std::basic_ostream<charT, traits> &os, 
    const streamstate<State> &s) 
{ 
    s.modify(os); 
    return os; 
} 

// convenience extractor for istream classes 
template 
< 
    class State, 
    class charT, 
    class traits 
> 
std::basic_istream<charT, traits> & 
operator>>(std::basic_istream<charT, traits> &is, 
    const streamstate<State> &s) 
{ 
    s.modify(is); 
    return is; 
} 

// the alternative if there is a need to have 
// many different state values of the same type 
// here, the instance of streamstate_value encapsulates 
// the access information (the slot index) 

template 
< 
    class State, 
    class charT = char, 
    class traits = std::char_traits<char> 
> 
class streamstate_value 
{ 
public: 

    streamstate_value() 
     : index_(-1) 
    { 
    } 

    // returns a reference to current (possibly default) state 
    State & get(std::basic_ios<charT, traits> &ios) 
    { 
     long *errslot; 
     void *&p = state_slot(ios, errslot, index_); 

     // propagate the error flag to the real stream state 
     if (*errslot == std::ios_base::badbit) 
     { 
      ios.setstate(std::ios_base::badbit); 
      *errslot = 0; 
     } 

     // this function returns a reference and the only 
     // sensible way of error reporting is via exception 
     if (ios.bad()) 
      throw StreamStateException(); 

     if (p == NULL) 
     { 
      // create default state if this is new slot 
      p = new State; 
      ios.register_callback(state_callback, index_); 
     } 

     return *static_cast<State*>(p); 
    } 

private: 

    // manages the destruction and format copying 
    // (in the latter case performs deep copy of the state) 
    static void state_callback(std::ios_base::event e, 
     std::ios_base &ios, int index) 
    { 
     long *errslot; 
     if (e == std::ios_base::erase_event) 
     { 
      // safe delete if state_slot fails 
      delete static_cast<State*>(state_slot(ios, errslot, index)); 
     } 
     else if (e == std::ios_base::copyfmt_event) 
     { 
      void *& p = state_slot(ios, errslot, index); 
      State *old = static_cast<State*>(p); 

      // Standard forbids any exceptions from callbacks 
      try 
      { 
       // in-place deep copy 
       p = new State(*old); 
    } 
      catch (...) 
      { 
       // clean the value slot and set the error flag 
       // in the error slot 
       p = NULL; 
       *errslot = std::ios_base::badbit; 
      } 
     } 
    } 

    // returns the references to associated slot 
    static void *& state_slot(std::ios_base &ios, 
     long *& errslot, int & index) 
    { 
     if (index < 0) 
     { 
      // first index usage 
      index = std::ios_base::xalloc(); 
     } 

     void *&p = ios.pword(index); 
     errslot = &(ios.iword(index)); 

     // note: if pword failed, 
     // then p is a valid void *& initialized to 0 
     // (27.4.2.5/5) 

     return p; 
    } 

    int index_; 
}; 

// partial specialization for iword functionality 
template 
< 
    class charT, 
    class traits 
> 
class streamstate_value<long, charT, traits> 
{ 
public: 
    // construct with the default state value 
    streamstate_value() 
     : index_(-1) 
    { 
    } 

    long & get(std::basic_ios<charT, traits> &ios) 
    { 
     if (index_ < 0) 
     { 
      // first index usage 
      index_ = std::ios_base::xalloc(); 
     } 

     long &s = ios.iword(index_); 
     if (ios.bad()) 
      throw StreamStateException(); 

     return s; 
    } 

private: 
    long index_; 
}; 

#endif // STREAMSTATE_H_INCLUDED 
+0

Bibliothek Link ist tot. –

+0

@BenFulton. Tatsächlich.Leider scheint die Bibliothek nicht mehr verfügbar zu sein. IIRC, ich habe irgendwo eine Kopie des Zip. Und hier ist es. Es kann jedoch besser sein, sich auf Boost zu konzentrieren. –

0

litb Ansatz ist „der richtige Weg“ und nece für komplizierte Sachen, aber so etwas kann gut genug sein. Fügen Sie Privatsphäre und Freundschaft hinzu, um zu schmecken.

struct PlusOne 
{ 
    PlusOne(int i) : i_(i) { } 
    int i_; 
}; 

std::ostream & 
operator<<(std::ostream &o, const PlusOne &po) 
{ 
    return o << (po.i_ + 1); 
} 

std::cout << "1 + 1 = " << PlusOne(num2); // => "1 + 1 = 2" 

In diesem einfachen Beispiel das Erstellen und Streaming ein temporäres Objekt nicht viel hilfreicher scheinen eine Funktion PlusOne() als jemand bereits vorgeschlagen, als definieren. Aber angenommen, Sie wollten es so arbeiten:

std::ostream & 
operator<<(std::ostream &o, const PlusOne &po) 
{ 
    return o << po.i_ << " + 1 = " << (po.i_ + 1); 
} 

std::cout << PlusOne(num2); // => "1 + 1 = 2" 
2

Es ist keine direkte Antwort auf Ihre Frage, aber glauben Sie nicht, dass eine einfache alte Funktion ist sowohl einfacher zu implementieren und klarer zu benutzen als ein Schreiben Voller Manipulator?

#include <sstream> 

template<typename T> 
std::string plusone(T const& t) { 
    std::ostringstream oss; 
    oss << (t + 1); 
    return oss.str(); 
} 

Verbrauch:

cout << plusone(42); 

von „clear to use“, ich meine, dass der Benutzer nicht selbst zu fragen: „Ist es nur das nächste Element oder alle nachfolgenden Elemente beeinflussen? " Aus der Inspektion ist ersichtlich, dass nur das Argument der Funktion betroffen ist.

(Für den plusone() Beispiel könnten Sie vereinfachen noch weiter, indem nur eine T statt Rückkehr, aber Rückkehr ein std::string dient den allgemeinen Fall.)

Verwandte Themen