2009-04-28 10 views
48

In C++, per stampare un numero in esadecimale si esegue questa operazione:C++ flusso personalizzato manipolatore che cambia voce successiva in funzione

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

so di poter creare un manipolatore che aggiunge solo roba per il flusso in questo modo:

std::ostream& windows_feed(std::ostream& out) 
{ 
    out << "\r\n"; 
    return out; 
} 

std::cout << "Hello" << windows_feed; // => "Hello\r\n" 

Tuttavia, come posso creare un manipolatore che, come 'hex', modifica gli elementi di venire sul torrente? Come semplice esempio, come faccio a creare il manipolatore plusone qui ?:

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" 

risposta

62

Innanzitutto, è necessario memorizzare alcuni stati in ogni flusso. È possibile farlo con la funzione iword e un indice si passa ad essa, data dalla xalloc:

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; } 

Avendo quella in atto, si può già recuperare uno stato in tutti i corsi d'acqua. Ora, devi solo collegare alla rispettiva operazione di uscita. L'output numerico viene eseguito da un facet, poiché potenzialmente è dipendente dalle impostazioni internazionali.Quindi puoi fare

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())); 
    } 
}; 

Ora puoi testare il materiale.

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

Se si desidera che solo il numero successivo viene incrementato, è sufficiente impostare la parola per 0 di nuovo dopo ogni chiamata a do_put.

+2

+1. Questa è la soluzione completa, dal libro (e come puoi vedere, abbastanza complessa). Ma mi chiedo se non sarebbe più semplice (e possibilmente più chiaro) creare semplicemente una funzione plusone() che accetta un argomento e restituisce il risultato? –

+1

esposizione molto bella! –

+0

Grazie gente :) –

-1

I hex, dec e oct manipolatori semplicemente modificare la proprietà basefield dell'esistente stream.

Vedere C++ Reference per ulteriori informazioni su questi manipolatori.

Come pubblicato in Neil Butterworth's answer, è necessario estendere le classi di flusso esistenti o crearne di proprie per disporre di manipolatori che influiscono sui valori futuri inseriti nello stream.

Nell'esempio del manipolatore plusone, l'oggetto flusso dovrebbe avere un contrassegno interno per indicare che è necessario aggiungerne uno a tutti i valori inseriti. Il manipolatore plusone avrebbe semplicemente impostato quel flag e il codice per gestire l'inserimento dello stream avrebbe controllato quel flag prima di inserire i numeri.

+1

devono -1 dispiace - iostreams contengono un meccanismo di estensibilità (xalloc(), iword(), pword()) come sottolineato da litb. –

+2

Non c'è bisogno di scusarsi. Avevo chiaramente torto, e non avrei saputo senza vedere il -1. Grazie per averlo portato alla mia attenzione! –

11

Sono assolutamente d'accordo con Neil Butterworth su questo, tuttavia nel caso specifico si sta utilizzando si potrebbe fare questo hack totalmente orribile. Non farlo in alcun codice di produzione. Ha un sacco di bug. Per prima cosa funziona solo nella tua one-liner sopra, non cambia lo stato del flusso sottostante.

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

Mi hai battuto :) –

1

ho creato una soluzione semplice per il vostro caso di test senza utilizzare <iomanip>. Non posso promettere che lo stesso approccio funzionerà nella vita reale.

L'approccio di base è che cout << plusone restituisce un oggetto temporaneo ausiliario (PlusOnePlus), che a sua volta ha l'overload operator << che esegue l'aggiunta.

ho testato su Windows:

PlusOne plusone; 
cout << plusone << 41 

produce "42", come previsto. Ecco il codice:

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: Commentate il codice a precisare che sto contando sul costruttore di copia di default (etc.) per PlusOnePlus. Una robusta implementazione probabilmente definirà questi

+0

Intelligente, ma nota che quanto segue non funzionerà: "os << plusone; os << 41;" –

+0

Potrei sostenere che questa è una caratteristica :-) anche se, ammettiamolo, è incoerente con il modo standard in cui operano i manipolatori. –

+1

Sì, dubito che qualcuno noterà un'altra incoerenza gratuita in C++ ...;) +1. –

1

Dovrai giocare con streamstates. Ho bookmarked i seguenti link sul tema:

Come biblioteca Maciej Sobczak non è più disponibile on-line è, e come la patente mi permette di farlo, (corretta me se sbaglio), qui è una copia del suo file principale che sono riuscito a salvare dall'oblio: approccio

// 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

Il collegamento alla biblioteca è morto. –

+0

@BenFulton. Infatti.Sfortunatamente la libreria non sembra più disponibile. IIRC, ho una copia della zip da qualche parte. Ed eccolo qui. Tuttavia, potrebbe essere meglio tornare indietro per migliorare al giorno d'oggi. –

0

litb è "la strada giusta" e nece ssary per cose complicate, ma qualcosa del genere può essere abbastanza buono. Aggiungi privacy e amicizia a piacere.

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 questo semplice esempio la creazione e lo streaming di un oggetto temporaneo non sembra molto più utile di definire una funzione plusone() come qualcuno già suggerito. Ma si supponga di voler farlo funzionare in questo modo:

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

Non è una risposta diretta alla sua domanda, ma non pensi che l'utilizzo di una funzione vecchia pianura è al tempo stesso semplice da implementare e più chiara da usare che scrivere un manipolatore in piena regola?

#include <sstream> 

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

Usage:

cout << plusone(42); 

da elementi "chiari di usare", voglio dire che l'utente non ha bisogno di chiedere se stessi "Influisce solo l'elemento successivo, o tutte le successive? " È evidente dall'ispezione che è interessato solo l'argomento della funzione.

(Per l'esempio plusone(), si potrebbe semplificare ulteriormente solo restituendo un T invece, ma restituire un std::string serve il caso generale.)

Problemi correlati