2010-04-03 7 views
12

Implementé un ostream para la salida de depuración que envía termina enviando la información de depuración al OutputDebugString. Un uso típico de ella se parece a esto (donde debug es un objeto ostream):¿Deseos solo de depuración en C++?

debug << "some error\n"; 

Para las versiones de lanzamiento, lo que es la forma menos dolorosa y mas potente si no desea imprimir estas instrucciones de depuración?

Respuesta

7

¿Qué tal esto? Habría que comprobar que en realidad se optimiza para nada en la liberación:

#ifdef NDEBUG 
    class DebugStream {}; 
    template <typename T> 
    DebugStream &operator<<(DebugStream &s, T) { return s; } 
#else 
    typedef ostream DebugStream; 
#endif 

que tendrá que pasar el objeto de secuencia de depuración como DebugStream &, no como un ostream &, ya que en las versiones de lanzamiento no es uno. Esto es una ventaja, ya que si la secuencia de depuración no es ostream, eso significa que no incurre en la penalización de tiempo de ejecución habitual de una secuencia nula que admite la interfaz ostream (funciones virtuales que realmente reciben llamadas pero no hacen nada).

Advertencia: Acabo de inventar esto, normalmente haría algo similar a la respuesta de Neil: tener una macro que significa "solo hacer esto en compilaciones de depuración", para que sea explícito en la fuente qué es el código de depuración, y qué no es Algunas cosas que realmente no quiero abstraer.

La macro de Neil también tiene la propiedad de que, definitivamente, no evalúa sus argumentos en el lanzamiento. Por el contrario, incluso con mi plantilla inline, se encuentra que a veces:

debug << someFunction() << "\n"; 

no se pueden optimizar para nada, ya que el compilador no tiene por qué saber que someFunction() no tiene efectos secundarios. Por supuesto, si someFunction()tiene tiene efectos secundarios, entonces es posible que desee que se invoque en compilaciones de lanzamiento, pero esa es una mezcla peculiar de logging y código funcional.

+0

¡Gracias! Estaba empezando a pensar algo así y me alegra ver que no soy el único que lo estaba pensando. Lo probaré el lunes en el trabajo y veré qué tan bien el compilador puede optimizar la transmisión. – Emanuel

+0

Cualquiera que use este método, tenga en cuenta que algunos elementos no se optimizarán en el modo de lanzamiento. Si tiene: '' 'debug << someCPUIntensiveFunctionOrFunctionWhichMayAffectState() <<" \ n "' '' aún se ejecutará en modo de lanzamiento. Me perdí este cavet la primera vez –

9

La forma más común (y desde luego los mas potente) es para eliminarlos mediante el preprocesador, usando algo como esto (más simple posible implementación):

#ifdef RELEASE 
    #define DBOUT(x) 
#else 
    #define DBOUT(x) x 
#endif 

A continuación, puede decir

DBOUT(debug << "some error\n"); 

Edit: Por supuesto, puede hacer DBOUT un poco más complejo:

#define DBOUT(x) \ 
    debug << x << "\n" 

que permite una sintaxis algo más agradable:

DBOUT("Value is " << 42); 

Una segunda alternativa es definir dbout a ser la corriente. Esto significa que debe implementar algún tipo de clase de flujo nulo - consulte Implementing a no-op std::ostream. Sin embargo, dicha secuencia tiene una sobrecarga de tiempo de ejecución en la versión de lanzamiento.

+0

Tenía la esperanza de que hubiera alguna forma de conservar la buena sintaxis de iostream y también optimizara las declaraciones en versiones de lanzamiento como las macros. – Emanuel

3

Como otros han dicho, la forma más eficaz es utilizar el preprocesador. Normalmente evito el preprocesador, pero este es el único uso válido que he encontrado para proteger los encabezados.

Normalmente quiero la capacidad de activar cualquier nivel de rastreo en los ejecutables de lanzamiento, así como en los ejecutables de depuración.Los ejecutables de depuración obtienen un nivel de rastreo predeterminado más alto, pero el nivel de rastreo se puede establecer por archivo de configuración o dinámicamente en tiempo de ejecución.

Con este fin mis macros parecen

#define TRACE_ERROR if (Debug::testLevel(Debug::Error)) DebugStream(Debug::Error) 
#define TRACE_INFO if (Debug::testLevel(Debug::Info)) DebugStream(Debug::Info) 
#define TRACE_LOOP if (Debug::testLevel(Debug::Loop)) DebugStream(Debug::Loop) 
#define TRACE_FUNC if (Debug::testLevel(Debug::Func)) DebugStream(Debug::Func) 
#define TRACE_DEBUG if (Debug::testLevel(Debug::Debug)) DebugStream(Debug::Debug) 

Lo bueno acerca del uso de una sentencia if es que no hay ningún costo para para el rastreo que no se emite, el código de rastreo sólo se obtiene la llamada si será impreso.

Si no desea que un determinado nivel no aparezca en las compilaciones de lanzamiento, utilice una constante que esté disponible en tiempo de compilación en la sentencia if.

#ifdef NDEBUG 
    const bool Debug::DebugBuild = false; 
#else 
    const bool Debug::DebugBuild = true; 
#endif 

    #define TRACE_DEBUG if (Debug::DebugBuild && Debug::testLevel(Debug::Debug)) DebugStream(Debug::Debug) 

Esto mantiene la sintaxis iostream, pero ahora el compilador optimizar la sentencia if fuera del código, en las versiones de lanzamiento.

+0

¡El uso de declaraciones if no es una mala idea! Sé que si las declaraciones en macros pueden tener algunos inconvenientes, entonces tendría que ser especialmente cuidadoso al construirlos y usarlos. Por ejemplo: if (error) TRACE_DEBUG << "error"; else do_something_for_success(); Terminaría ejecutando do_something_for_success() si se produce un error y las instrucciones de seguimiento de nivel de depuración están deshabilitadas porque la instrucción else se une con la instrucción if interna. Sin embargo, la mayoría de los estilos de codificación exigen el uso de llaves que resolverían el problema. if (error) { TRACE_DEBUG << "error"; } else do_something_for_success(); – Emanuel

+0

La forma más segura de hacer una macro es envolverla dentro de {macro_here} while (0); –

1

@iain: Se quedó sin espacio en el cuadro de comentarios, por lo que lo publicamos aquí para mayor claridad.

¡El uso de sentencias if no es una mala idea! Sé que si las declaraciones en macros pueden tener algunos inconvenientes, entonces tendría que ser especialmente cuidadoso al construirlos y usarlos. Por ejemplo:

if (error) TRACE_DEBUG << "error"; 
else do_something_for_success(); 

... terminaría ejecutar do_something_for_success() si se produce un error y instrucciones de seguimiento de nivel de depuración están desactivadas debido a que la sentencia else se une con el interior de la sentencia if. Sin embargo, la mayoría de los estilos de codificación exigen el uso de llaves que resolverían el problema.

if (error) 
{ 
    TRACE_DEBUG << "error"; 
} 
else 
{ 
    do_something_for_success(); 
} 

En este fragmento de código, do_something_for_success() no se ejecuta erróneamente si el rastreo de nivel de depuración está desactivada.

+0

@Emanuel, sí, debes tener cuidado con esto, siempre uso corchetes para mis sentencias if y para bucles. He visto errores en los que las personas agregaron una nueva línea a la parte posterior de un enunciado if, pero se olvidó de agregar los curlies, el código estaba muy sangrado, por lo que el error era casi imposible de detectar. – iain

2
#ifdef RELEASE 
    #define DBOUT(x) 
#else 
    #define DBOUT(x) x 
#endif 

Simplemente use esto en los propios operadores de ostream. Incluso podría escribir un solo operador para eso.

template<typename T> Debugstream::operator<<(T&& t) { 
    DBOUT(ostream << std::forward<T>(t);) // where ostream is the internal stream object or type 
} 

Si el compilador no puede optimizar las funciones vacías en el modo de disparo, entonces es el momento de conseguir un nuevo compilador.

Por supuesto, utilicé las referencias de rvalue y el reenvío perfecto, y no hay garantía de que tenga dicho compilador. Pero, seguramente puede usar una constante de referencia si su compilador solo es compatible con C++ 03.

5

Un método más bonita:

#ifdef _DEBUG 
#define DBOUT cout // or any other ostream 
#else 
#define DBOUT 0 && cout 
#endif 

DBOUT << "This is a debug build." << endl; 
DBOUT << "Some result: " << doSomething() << endl; 

Mientras usted no hace nada raro, las funciones de llamada y pasada a DBOUT no serán llamados en las versiones de lanzamiento. Esta macro funciona debido a la precedencia del operador y al AND lógico; porque && tiene una prioridad menor que <<, compilaciones de versiones compila DBOUT << "a" como 0 && (cout << "a"). El AND lógico no evalúa la expresión de la derecha si la expresión de la izquierda se evalúa como cero o false; debido a que la expresión de la mano izquierda siempre se evalúa como cero, la expresión de la mano derecha siempre es eliminada por cualquier compilador que valga la pena usar, excepto cuando toda la optimización está desactivada (e incluso entonces, el código obviamente inalcanzable puede ignorarse).)


Aquí es un ejemplo de las cosas extrañas que rompan esta macro:

DBOUT << "This is a debug build." << endl, doSomething(); 

Ver las comas. doSomething() siempre se llamará, independientemente de si se define _DEBUG o no. Esto se debe a que la declaración se evalúa en las versiones de lanzamiento como:

(0 && (cout << "This is a debug build." << endl)), doSomething(); 
// evaluates further to: 
false, doSomething(); 

Para utilizar comas con esta macro, la coma debe ser envuelto en paréntesis, así:

DBOUT << "Value of b: " << (a, b) << endl; 

Otro ejemplo:

(DBOUT << "Hello, ") << "World" << endl; // Compiler error on release build 

En las versiones de lanzamiento, esto se evalúa como:

(0 && (cout << "Hello, ")) << "World" << endl; 
// evaluates further to: 
false << "World" << endl; 

que causa un error de compilación porque bool no se puede desplazar a la izquierda mediante un puntero char a menos que se haya definido un operador personalizado. Esta sintaxis también causa problemas adicionales:

(DBOUT << "Result: ") << doSomething() << endl; 
// evaluates to: 
false << doSomething() << endl; 

al igual que cuando se utiliza la coma mal, doSomething() todavía es llamado, ya que su resultado tiene que ser pasado al operador de izquierda-turno. (Esto sólo puede ocurrir cuando un operador personalizada se define que la izquierda se desplaza un bool por un puntero char;. De lo contrario, se produce un error de compilación)

No DBOUT << ... un paréntesis. Si quiere poner en paréntesis un cambio de enteros literal, entonces entre paréntesis, pero no conozco una sola buena razón para poner en paréntesis un operador de flujo.

+1

Esta es una buena idea y funcionó perfectamente. – martin

+1

Exactamente lo que imaginé que podría verse y lo que estaba buscando. Muchas gracias ! – user1913596

Cuestiones relacionadas