2010-08-16 14 views
5

Escribo una clase de registro en C++. Esta clase es un singleton. Quiero añadir registros de tal manera:operator << - cómo detectar el último argumento

Log::GetInstance() << "Error: " << err_code << ", in class foo"; 

Ok, y dentro de un objeto de registro, quiero salvar toda esta línea en el momento en que el último argumento viene ("en la clase foo" en este ejemplo)

Cómo detectar el último < < argumento? < < a b < < < < is_this_last < < maybe_this_is < < or_not.

No uso ninguna etiqueta de cierre.

+1

No creo que el operador de sobrecarga << es lo que quiere aquí – Falmarri

+1

@Falmarri: Realmente me gusta este enfoque. Es como Qt usa su clase 'QDebug'. – Job

+1

Y QT funciona en contra del lenguaje. El hecho de que puedan no significa que deberían hacerlo. –

Respuesta

17

Puede resolver este problema si no utiliza un singleton. Si realiza una función como esta:

Log log() 
{ 
    return Log(); 
} 

se puede añadir un registro de casi la misma manera que lo hizo antes:

log() << "Error: " << err_code << ", in class foo"; 

La diferencia es que el destructor del objeto Log se llama después de esta línea . Entonces ahora tiene una forma de detectar cuándo se procesó el último argumento.

+0

Algunos objetos todavía tienen que mantener el manejador de archivo abierto ... – Potatoswatter

+0

@Potatoswatter: Sí. Como estaba usando un singleton, yo diría que es un miembro estático de la clase 'Log'. Otras soluciones son por supuesto posibles. – Job

+0

Me gustan las técnicas RAII jugando con constructor y destructor, creo que es realmente elegante. –

9

Me gustaría que tu Log::GetInstance devuelva un objeto proxy en lugar del propio objeto de registro. El objeto proxy guardará los datos que se escriben en él, y luego en su destructor, realmente escribirá los datos acumulados en el registro.

+0

+1: Aunque los singletons pueden ser feos, es mejor dar consejos que requieren menos refactorizaciones innecesarias. – Potatoswatter

1

No se ponga demasiado listo con sus operadores. Debe sobrecargar a los operadores cuando tenga sentido hacerlo. Aquí no deberías. Eso se ve raro.

sólo debe tener un método estático que tiene este aspecto:

Log::Message(message_here); 

que toma un std :: string. Entonces los clientes tienen el dolor de cabeza de averiguar cómo armar la cadena de error.

+4

¿Por qué es extraño? Las transmisiones en C++ funcionan exactamente de la misma manera. – Job

+0

@Job: No, las transmisiones en C++ no. Específicamente, no hacen nada especial en la última invocación. Un objeto de flujo C++ utiliza 'operator <<()' para ejecutar una función de un argumento, y devuelve un objeto de flujo C++ que puede ejecutar nuevamente una función. La sobrecarga del operador es ordenada, pero no es muy flexible o extensible, y hay casos en los que agregar otro requisito hace que el operador sobrecargue una mala idea. –

+0

@David: Pero la respuesta no es hablar de lo que 'operador <<' hace, solo que hacer 'operator <<' en primer lugar es extraño. Lo cual no es – GManNickG

4

Hace que Log devuelva un objeto diferente después del operador < <.

template<typename T> 
LogFindT operator<<(Log aLog, T const& data) 
{ 
    // Put stuff in log. 
    log.putStuffInLog(data); 

    // now return the object to detect the end of the statement. 
    return LogFindT(aLog); 
} 


struct LogFindT 
{ 
    LogFindT(Log& aLog) : TheLog(aLog) {} 
    Log& TheLog; 
    ~LogFindT() 
    { 
     // Do stuff when this object is eventually destroyed 
     // at the end of the expression. 
    } 
}; 

template<typename T> 
LogFindT& operator<<(LogFindT& aLog, T const& data) 
{ 
    aLog.TheLog.putStuffInLog(data); 

    // Return a reference to the input so we can chain. 
    // The object is thus not destroyed until the end of the stream. 
    return aLog; 
} 
4

creo que Jerry y Martin han dado la mejor sugerencia, pero en aras de la exhaustividad, lo primero que pensé fue std::endl.

Si implementado Log dentro del sistema iostream por una clase personalizada streambuf, a continuación, simplemente puede añadir << endl o << flush al final de la línea. Ya que estás preguntando, supongo que no lo hiciste.

Pero puede imitar la forma en que funciona endl.O bien agregar un controlador de manipulador

Log &operator<< (Log &l, Log & (*manip)(Log &)) 
    { return manip(l); } // generically call any manipulator 

Log &flog(Log &l) // define a manipulator "flush log" 
    { l->flush(); return l; } 

o añadir un dedicado operator<<

struct Flog {} flog; 

Log &operator<< (Log &l, Flog) 
    { l->flush(); return l; } 
0

No hay una buena manera de hacer lo que quiera. C y C++ simplemente no son lenguajes orientados a líneas. No hay una unidad más grande como una "línea de código" ni nada, ni se combinan las llamadas encadenadas de ninguna manera.

En C++, la expresión "un < < b < < c < < d" es exactamente equivalente a tres llamadas separadas a operador < <, así:

t1 = a; 
t2 = t1.operator<<(b); 
t3 = t2.operator<<(c); 
t4 = t3.operator<<(d); 

Por eso C++ ostreams uso endl como una marcador explícito de final de línea; simplemente no hay una forma decente de hacerlo de otra manera.

+0

Por cierto, aprecio todas las otras respuestas que surgieron con formas de hacerlo. Crear temporales y proxies de corta duración es una diversión académica divertida, pero en la práctica generará una sobrecarga adicional en el tiempo de ejecución y complicará la depuración (¡de su sistema de registro y depuración!) si algo sale mal. Yo realmente no quería ir allí, porque si usted no entiende lo suficientemente bien como para llegar a la idea de sí mismo, es sólo una forma más complicada para disparar en el pie. Así que siguen en pie por mi afirmación - no hay buena * * manera de hacer lo que quiera. :-) Así que, básicamente –

+2

lugar de enseñarles que usted dice "no se podía entenderlo, no lo intente."? Además, estos proxies estarán completamente entre líneas por cualquier compilador hecho en la última década, no hay ninguna sobrecarga. Incluso si lo hubiera, una interfaz limpia es mejor que una rápida. (Uno siempre puede hacer una interfaz limpia rápido * con un perfilador * (no adivinando), pero una interfaz rápida es mucho más difícil hacer limpia.) – GManNickG

+1

discrepo (junto a Drew). Crear temporarios no es una práctica académica, pero se ve con bastante frecuencia en el código comercial. No hay una sobrecarga significativa (otro de los rumores de Internet) y no es tan difícil de depuración (ya que es relativamente fácil de conseguir escribir primera vez (por lo que no hay errores)). –

Cuestiones relacionadas