2012-03-07 11 views
12

Nel nostro progetto usiamo l'operatore di flusso C++ (< <) nel nostro modello a oggetti per stampare un formato dei dati facilmente leggibile. Un esempio semplificato è questo:Come aggiungere l'indentazione all'operatore dello streaming

std::ostream& operator<<(std::ostream & oStream, const OwnClass& iOwnClass) { 
    oStream << "[SomeMember1: " << iOwnClass._ownMember1 << "]\n"; 
    oStream << "[SomeMember2: " << iOwnClass._ownMember2 << "]\n"; 
} 

Con conseguente questo nella registrazione:

[SomeMember1: foo] 
[SomeMember2: bar] 

Quello che vogliamo ora è quello di essere in grado di rientrare il risultato di tale operatore. Alcune classi chiamanti potrebbero non volere il risultato in questo modo, ma vogliono aggiungere 2 indention di spazi prima di ogni riga. Potremmo aggiungere un membro alla nostra classe specificando l'indention, ma non sembra essere una soluzione elegante.

Ovviamente questo non è un grosso problema, ma il nostro registro sarebbe molto più bello se funzionasse.

Grazie

risposta

21

La soluzione più semplice è quella di inserire uno streambuf filtraggio tra ostream e lo streambuf effettivo. Qualcosa di simile:

class IndentingOStreambuf : public std::streambuf 
{ 
    std::streambuf*  myDest; 
    bool    myIsAtStartOfLine; 
    std::string   myIndent; 
    std::ostream*  myOwner; 
protected: 
    virtual int   overflow(int ch) 
    { 
     if (myIsAtStartOfLine && ch != '\n') { 
      myDest->sputn(myIndent.data(), myIndent.size()); 
     } 
     myIsAtStartOfLine = ch == '\n'; 
     return myDest->sputc(ch); 
    } 
public: 
    explicit   IndentingOStreambuf( 
          std::streambuf* dest, int indent = 4) 
     : myDest(dest) 
     , myIsAtStartOfLine(true) 
     , myIndent(indent, ' ') 
     , myOwner(NULL) 
    { 
    } 
    explicit   IndentingOStreambuf(
          std::ostream& dest, int indent = 4) 
     : myDest(dest.rdbuf()) 
     , myIsAtStartOfLine(true) 
     , myIndent(indent, ' ') 
     , myOwner(&dest) 
    { 
     myOwner->rdbuf(this); 
    } 
    virtual    ~IndentingOStreambuf() 
    { 
     if (myOwner != NULL) { 
      myOwner->rdbuf(myDest); 
     } 
    } 
}; 

Per inserire, basta creare un'istanza della streambuf:

IndentingOStreambuf indent(std::cout); 
// Indented output... 

Quando indent va fuori portata, tutto torna alla normalità.

(Per la registrazione, Ho uno che è un po 'più complesso: il LoggingOStreambuf prende __FILE__ e __LINE__ come argomenti, imposta myIndent ad una stringa formattata con questi argomenti, più un timestamp , resetta ad una rientranza stringa dopo ogni uscita, raccoglie tutto l'output in un std::ostringstream, e l'emette atomicamente a myDest nel distruttore.)

+0

ha funzionato perfettamente! Ho apportato alcune modifiche, ad esempio aggiungendo un metodo increaseIndent e decreaseIndent. I miei registri sembrano esattamente come li voglio ora. Grazie. –

+0

@James: avresti il ​​codice più complesso disponibile per favore? – Cookie

1

Non così buon modo per farlo è quello di aggiungere una variabile globale, che racconta il rientro. Qualcosa del genere:

E quindi impostare come appropriato.

+2

Se si desidera solo il rientro quando l'output di un tipo specifico, il cui 'operatore <<' si definisce , quindi puoi usare 'std :: ios_base :: xalloc' e' std :: ios_base :: iword' t o mantenere una indentazione per flusso. Se si desidera il rientro anche quando si esegue l'output di 'dest <<" una stringa "', tuttavia, è necessario utilizzare un filtro di flusso. –

+0

@JamesKanze: buon punto - lo stesso vale per la mia soluzione - non ci ha pensato. –

+0

Questo funzionerebbe sicuramente, ma non mi piacciono le variabili globali, soprattutto quando sono necessarie solo per formattare i miei registri :) –

0

È possibile creare una propria classe di stream con una variabile di indentazione e sovrascrivere l'endl per quella classe, inserendo il rientro.

+2

Quale non rientri il prima uscita (può o non può essere una caratteristica), inserirà spazi dopo l'ultima riga (comportamento indefinito se il file è aperto in modalità testo), e non indurrà quando viene visualizzato ''\ n''. E ovviamente, dovrai definire tutti gli operatori << << '. Questo è un lavoro per 'streambuf', non per la classe' ostream'. –

+0

Ho pensato anche a questo, ma l'ostream a cui devo scrivere non è nelle mie mani, quindi non posso cambiare il suo tipo. Devo usare l'ostream dalla nostra lib di registrazione. Lo streambuf ha funzionato. –

+0

@W. Goeman: puoi derivare da ostream, ha metodi virtuali. – Dani

1

Questo può essere fatto utilizzando un manipolatore di flusso personalizzato che memorizza il livello di indentazione desiderato in una parola della matrice interna estensibile del flusso. È possibile richiedere una parola di questo tipo utilizzando la funzione ios_base::xalloc. Questa funzione ti darà l'indice della tua parola. È possibile accedervi utilizzando ios_base::iword. Un modo per attuare tale sarebbe questa:

struct indent { 
    indent(int level) : level(level) {} 
private: 
    friend std::ostream& operator<<(std::ostream& stream, const indent& val); 

    int level; 
}; 

std::ostream& operator<<(std::ostream& stream, const indent& val) { 
    for(int i = 0; i < val.level; i++) { 
     stream << " "; 
    } 
    return stream; 
} 

std::ostream& operator<<(std::ostream & oStream, const OwnClass& iOwnClass) { 
    oStream << indent(oStream.iword(index)) << "[SomeMember1: " << 
       iOwnClass._ownMember1 << "]\n"; 
    oStream << indent(oStream.iword(index)) << "[SomeMember2: " << 
       iOwnClass._ownMember2 << "]\n"; 
} 

Dovresti capire dove memorizzare il index. Ciò consente effettivamente di aggiungere uno stato personalizzato allo stream (si noti che questo non sarebbe sicuro per i thread pronto all'uso). Ogni funzione che richiede il rientro dovrebbe aggiungere il rientro richiesto allo stream e sottrarlo di nuovo quando viene eseguito. Si potrebbe fare in modo che questo accada sempre utilizzando una guardia per aggiungere/sottrarre il trattino desiderato (IMHO questo è più elegante rispetto all'utilizzo di un manipolatore):

class indent_guard { 
public: 
    indent_guard(int level, std::ostream& stream, int index) 
    : level(level), 
     stream(stream), 
     index(index) 
    { 
     stream.iword(index) += level; 
    } 

    ~indent_guard() { 
     stream.iword(index) -= level; 
    } 

private: 
    int level; 
    std::ostream& stream; 
    int index; 
}; 

si potrebbe usare in questo modo:

void some_func() { 
    indent_guard(2, std::cout, index); 

    // all output inside this function will be indented by 2 spaces 

    some_func(); // recursive call - output will be indented by 4 spaces 

    // here it will be 2 spaces again 
} 
+0

Almeno sai 'iostream' :-). Questo è chiaramente il modo di gestire qualsiasi manipolatore speciale per classi personalizzate. Non aiuta se il primo output non è un tipo definito da te; in questi casi, è necessario intercettare l'output al livello 'streambuf'. –

+0

Questo non risolve interamente il mio problema, ma è certamente un "buono a sapersi". Grazie. –

+0

Il tuo 'some_func()' causerà un overflow dello stack. – uckelman

Problemi correlati