2010-04-03 7 views
12

Ich habe eine ostream für die Debug-Ausgabe implementiert, die sendet sendet die Debug-Informationen an OutputDebugString. Eine typische Anwendung sieht es so aus (wo debug ist ein ostream-Objekt):Nur Debug-Ostreams in C++?

debug << "some error\n"; 

Für Release-Builds, was ist die am wenigsten schmerzhaft und performante Möglichkeit, nicht geben diese Debug-Anweisungen?

Antwort

7

Wie wäre es damit? Sie müßten überprüfen, dass es so gut wie nichts in Release tatsächlich optimiert:

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

Sie das Debug-Stream-Objekt als DebugStream &, nicht als Ostream &, übergeben müssen, da in Version baut es nicht ist ein. Dies ist ein Vorteil, denn wenn Ihr Debug-Stream kein Ostream ist, bedeutet dies, dass Sie nicht die übliche Laufzeitstrafe eines Nullstroms erleiden, der die Ostream-Schnittstelle unterstützt (virtuelle Funktionen, die tatsächlich aufgerufen werden, aber nichts tun).

Warnung: Ich habe gerade dies gemacht, normalerweise würde ich etwas ähnlich wie Neils Antwort tun - haben Sie eine Makrobedeutung "nur dies in Debug Builds", so dass es in der Quelle explizit ist, was ist Debugging-Code, und was ist nicht. Manche Dinge möchte ich eigentlich nicht abstrahieren.

Neils Makro hat auch die Eigenschaft, dass es absolut, definitiv, nicht seine Argumente in der Ausgabe auswertet. Im Gegensatz dazu sogar mit meiner Vorlage inlined, werden Sie feststellen, dass manchmal:

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

nicht zu nichts optimiert werden kann, da der Compiler nicht unbedingt wissen, dass someFunction() keine Nebenwirkungen hat. Natürlich, wenn someFunction()hat Nebenwirkungen haben, dann möchten Sie vielleicht, dass es in Release-Builds aufgerufen werden, aber das ist eine besondere Mischung aus Protokollierung und Funktionscode.

+0

Danke! Ich fing an, selbst etwas in dieser Richtung zu denken, und ich bin froh zu sehen, dass ich nicht der Einzige bin, der es dachte. Ich werde es am Montag bei der Arbeit ausprobieren und sehen, wie gut der Compiler den Stream optimieren kann. – Emanuel

+0

Jeder, der diese Methode verwendet, beachtet bitte den Punkt, dass einige Elemente im Freigabemodus nicht optimiert werden. Wenn Sie: '' 'debug << someCPUIntensiveFunctionOrFunctionWhichMayAffectState() <<" \ n "' '' es wird immer noch im Freigabemodus ausführen. Ich vermisste diese Cavet das erste Mal –

9

Die häufigste (und sicherlich die meisten performant) Art und Weise ist es, sich mit dem Prä-Prozessor zu entfernen, so etwas wie dies mit (möglichst einfacher Implementierung):

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

Sie dann

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

sagen können, Edit: Sie können natürlich machen DBout etwas komplexer:

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

die eine etwas schönere Syntax ermöglicht:

DBOUT("Value is " << 42); 

Eine zweite Alternative ist DBout zu definieren, um den Strom zu sein. Das bedeutet, dass Sie eine Art Null-Stream-Klasse implementieren müssen - siehe Implementing a no-op std::ostream. Ein solcher Stream hat jedoch im Release-Build einen Laufzeit-Overhead.

+0

Ich hatte gehofft, es gab einen Weg, der die schöne Iostream-Syntax behielt und auch die Anweisungen in Release-Builds wie die Makros optimiert hat. – Emanuel

3

Wie andere gesagt haben, ist der leistungsfähigste Weg, den Präprozessor zu verwenden. Normalerweise vermeide ich den Präprozessor, aber dies ist die einzige gültige Verwendung, die ich für die Bar-schützenden Header gefunden habe.

Normalerweise möchte ich die Möglichkeit, jede Ebene der Ablaufverfolgung in ausführbaren Dateien sowie ausführbare Dateien debuggen.Debug-Executables erhalten eine höhere Standard-Trace-Ebene, aber die Trace-Ebene kann über die Konfigurationsdatei oder dynamisch zur Laufzeit festgelegt werden.

Zu diesem Zweck meine Makros aussehen

#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) 

Die nette Sache über die Verwendung einer if-Anweisung ist, dass es zu keinen Kosten ist, dass zur Verfolgung nicht ausgegeben wird, Code die Verfolgung nur aufgerufen wird, wenn es sein wird, gedruckt.

Wenn Sie nicht möchten, dass eine bestimmte Ebene in Release-Builds nicht angezeigt wird, verwenden Sie eine Konstante, die zur Kompilierzeit in der if-Anweisung verfügbar ist.

#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) 

Dies hält die Iostream Syntax, aber jetzt wird der Compiler die if-Anweisung aus dem Code optimieren, in Release-Builds.

+0

Die Verwendung von if-Anweisungen ist keine schlechte Idee! Ich weiß, wenn Aussagen in Makros einige Tücken haben, muss ich besonders vorsichtig sein, sie zu konstruieren und zu benutzen. Zum Beispiel: if (Fehler) TRACE_DEBUG << "Fehler"; sonst do_something_for_success(); Würde do_something_for_success() enden, wenn ein Fehler auftritt und Trace-Anweisungen auf Debug-Ebene deaktiviert sind, weil die else-Anweisung mit der inneren if-Anweisung verknüpft ist. Die meisten Codierungsstile schreiben jedoch die Verwendung geschweifter Klammern vor, die das Problem lösen würden. if (Fehler) { TRACE_DEBUG << "Fehler"; } sonst do_something_for_success(); – Emanuel

+0

Der sicherste Weg, ein Makro zu machen, besteht darin, es in do {macro_here} einzubinden while (0); –

1

@iain: Im Kommentarfeld wurde kein Platz mehr angezeigt.

Die Verwendung von if-Anweisungen ist keine schlechte Idee! Ich weiß, wenn Aussagen in Makros einige Tücken haben, muss ich besonders vorsichtig sein, sie zu konstruieren und zu benutzen. Zum Beispiel:

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

... würde am Ende do_something_for_success() ausführen, wenn ein Fehler auftritt und Debug-Level-Trace-Anweisungen sind deaktiviert, da die else-Anweisung mit der inneren if-Anweisung bindet. Die meisten Codierungsstile schreiben jedoch die Verwendung geschweifter Klammern vor, die das Problem lösen würden.

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

In diesem Codefragment do_something_for_success(), wenn Debug-Level-Tracing deaktiviert ist nicht fehlerhaft ausgeführt wird.

+0

@Emanuel, ja du musst vorsichtig sein, ich verwende immer Klammern für meine if-Anweisungen und for-Schleifen. Ich habe Bugs gesehen, bei denen Leute eine neue Zeile zu dem dann Teil einer if-Anweisung hinzugefügt haben, aber vergessen haben, die Curlies hinzuzufügen, der Code wurde gut eingerückt, so dass Bug fast unmöglich zu erkennen war. – iain

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

Verwenden Sie dies nur in den tatsächlichen Ostream-Operatoren. Sie könnten sogar einen einzigen Operator dafür schreiben.

Wenn Ihr Compiler leere Funktionen im Freigabemodus nicht optimieren kann, ist es an der Zeit, einen neuen Compiler zu bekommen.

Ich habe natürlich rvalue Referenzen und perfekte Weiterleitung verwendet, und es gibt keine Garantie, dass Sie einen solchen Compiler haben. Aber, Sie können sicher nur eine const ref verwenden, wenn Ihr Compiler nur C++ 03-konform ist.

5

Eine schönere Methode:

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

Solange Sie tun nichts seltsam, Funktionen aufgerufen und weitergegeben DBOUT wird nicht aufgerufen werden in Release-Builds. Dieses Makro funktioniert aufgrund der Priorität des Operators und des logischen UND. Weil && eine niedrigere Priorität als << hat, kompilieren Sie Builds DBOUT << "a" als 0 && (cout << "a"). Das logische AND wertet den Ausdruck auf der rechten Seite nicht aus, wenn der linke Ausdruck null oder false ergibt; Da der linke Ausdruck immer zu Null führt, wird der rechte Ausdruck immer von jedem anderen Compiler entfernt, der es wert ist, verwendet zu werden, außer wenn alle Optimierungen deaktiviert sind (und selbst dann kann offensichtlich nicht erreichbarer Code weiterhin ignoriert werden).

)

Hier ist ein Beispiel für seltsame Dinge, die dieses Makro brechen:

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

Uhr der Kommas. doSomething() wird immer aufgerufen, unabhängig davon, ob _DEBUG definiert ist oder nicht. Dies liegt daran, die Aussage in Release ausgewertet baut als:

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

Um Komma mit diesem Makro zu verwenden, muss das Komma in Klammern eingewickelt werden, etwa so:

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

Ein weiteres Beispiel:

(0 && (cout << "Hello, ")) << "World" << endl; 
// evaluates further to: 
false << "World" << endl; 
:
(DBOUT << "Hello, ") << "World" << endl; // Compiler error on release build 

In Release-Builds, wird dies als ausgewertet

verursacht einen Compilerfehler, da bool nicht durch einen Zeiger char nach links verschoben werden kann, sofern kein benutzerdefinierter Operator definiert ist. Diese Syntax verursacht auch zusätzliche Probleme:

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

Genau wie wenn das Komma schlecht verwendet wurde, wird doSomething() noch genannt, weil ihr Ergebnis auf den linken Shift-Operator übergeben werden muss. (Dies kann nur auftreten, wenn ein benutzerdefinierter Operator definiert ist, dass links verschiebt ein bool durch einen char Zeiger;. Andernfalls ein Compiler-Fehler auftritt)

Sie DBOUT << ... nicht klammern. Wenn Sie eine literale Ganzzahlverschiebung in Klammern setzen möchten, dann klammern Sie sie in Klammern, aber mir ist kein einziger guter Grund bekannt, einen Stream-Operator in Klammern zu setzen.

+1

Das ist so eine nette Idee und es hat perfekt funktioniert. – martin

+1

Genau das, was ich mir vorgestellt habe, könnte aussehen und was ich gesucht habe. Danke vielmals ! – user1913596