2012-03-07 15 views
12

En nuestro proyecto utilizamos el operador de secuencia C++ (< <) en nuestro modelo de objetos para imprimir un formato de datos fácil de leer. Un ejemplo simplificado es la siguiente:Cómo agregar indention al operador de secuencia

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

Resultando en esto en el registro:

[SomeMember1: foo] 
[SomeMember2: bar] 

Lo que queremos ahora es ser capaz de sangrar el resultado de ese operador. Algunas clases de llamadas pueden no desear el resultado de esta manera, pero desean agregar 2 espacios de sangría antes de cada línea. Podríamos agregar un miembro a nuestra clase especificando la sangría, pero esa no parece ser una solución elegante.

Por supuesto, este no es un gran problema, pero nuestro registro sería mucho mejor si esto funcionara.

Gracias

Respuesta

21

La solución más simple es deslizar un streambuf de filtrado entre el ostream y el streambuf real. Algo así como:

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

Para insertar, basta con crear una instancia de la streambuf:

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

Cuando indent sale del ámbito, todo vuelve a la normalidad.

(Para el registro, tengo uno que es un poco más complejo: la LoggingOStreambuf toma __FILE__ y __LINE__ como argumentos, establece myIndent a una cadena con formato con estos argumentos, además de un sello de tiempo , restablece a una muesca cadena después de cada salida, recoge toda la producción en un std::ostringstream, y la envía a atómicamente myDest en el destructor.)

+0

¡Funcionó a la perfección! Sin embargo, hice algunos cambios, como agregar un método increaseIndent y decreaseIndent. Mis registros se ven exactamente como los quiero ahora. Gracias. –

+0

@James: ¿También tendrías el código más complejo disponible, por favor? – Cookie

1

No tan buena manera de hacer esto es añadir una variable global, que cuenta la sangría. Algo como esto:

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

Y luego configúrelo según corresponda.

+2

Si sólo desea la sangría cuando dé salida a un tipo específico, cuyo operador '' << definir , entonces puedes usar 'std :: ios_base :: xalloc' y' std :: ios_base :: iword' t o mantener una sangría por secuencia. Sin embargo, si desea la sangría incluso cuando da salida a 'dest <<" alguna cadena "', necesitará usar un streambuf filtrante. –

+0

@JamesKanze: Buen punto, lo mismo se aplica a mi solución, no pensé en eso. –

+0

Esto ciertamente funcionaría, pero realmente no me gustan las variables globales, especialmente cuando solo son necesarias para formatear mis registros :) –

0

Puede crear su propia clase de flujo que tenga una variable de sangría y anular el endl para esa clase, insertando la sangría.

+2

Que no sangrará el primer resultado (puede o no ser una función), insertará espacios después de la última línea (comportamiento indefinido si el archivo está abierto en modo texto) y no sangrará cuando se emita ''\ n''. Y, por supuesto, también deberá definir todos los operadores '<<'. Este es un trabajo para el 'streambuf', no para la clase' ostream'. –

+0

Pensé en esto también, pero el ostream en el que tengo que escribir no está en mis manos, así que no puedo cambiar su tipo. Debo usar el ostream de nuestra biblioteca de registro. El streambuf funcionó. –

+0

@W. Goeman: puedes derivar de ostream, tiene métodos virtuales. – Dani

1

Esto se puede hacer utilizando un manipulador de flujo personalizado que almacena el nivel de sangría deseado en una palabra de la matriz interna extensible de la secuencia. Puede solicitar tal palabra usando la función ios_base::xalloc. Esta función te dará el índice de tu palabra. Puede acceder usando ios_base::iword. Una forma de implementar esto sería la siguiente:

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

Habría que averiguar dónde almacenar el index. De hecho, esto le permite agregar un estado personalizado a la transmisión (tenga en cuenta que esto no sería seguro para subprocesos desde el primer momento). Cada función que desee una sangría debería agregar la sangría solicitada a la transmisión y restarla cuando esté terminada. Se podría asegurarse siempre pasa esto mediante el uso de un protector de añadir/restar el guión deseado (en mi humilde opinión esto es más elegante que el uso de un manipulador):

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

Usted puede utilizar de esta manera:

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

Al menos usted sabe 'iostream' :-). Esta es claramente la forma de manejar cualquier manipulador especial para clases personalizadas. No ayuda si la primera salida no es un tipo definido por usted mismo; en tales casos, debe interceptar la salida en el nivel 'streambuf'. –

+0

Esto no resolvió completamente mi problema, pero ciertamente es "bueno saberlo". Gracias. –

+0

Su 'some_func()' va a causar un desbordamiento de la pila. – uckelman

Cuestiones relacionadas