2013-07-15 6 views
8

Ich versuche, Protokollierung zu implementieren, die keinen Overhead erzeugen, wenn nicht benötigt (d. H. Kein Methodenaufruf sollte überhaupt durchgeführt werden). Ich möchte keinen Overhead, weil es Code mit niedriger Latenz ist. Habe ich nur noch #define ENABLE_LOGS meine Header-Klasse und jetzt sieht es so aus, dass (Sie können Details ignorieren)einfach wegwerfen C++ Anruf vollständig

#pragma once 

#include <string> 
#include <fstream> 

#define ENABLE_LOGS 

namespace fastNative { 

    class Logger 
    { 
    public: 
     Logger(std::string name_, std::string fileName, bool append = false); 
     ~Logger(void); 
     void Error(std::string message, ...); 
     void Debug(std::string message, ...); 
     void DebugConsole(std::string message, ...); 
     void Flush(); 
     static Logger errorsLogger; 
     static Logger infoLogger; 
    private: 
     FILE* logFile; 
     bool debugEnabled; 
    }; 

} 

Jedes Mal, ich brauche eine Methode verwenden ich es so, umgeben sollte:

#ifdef ENABLE_LOGS 
    logger.Debug("seq=%6d len=%4d", seq, length_); 
#endif 

Es ist error-phrone (ich kann vergessen zu umgeben) und macht den Code schmutzig. Kann ich meinen Code irgendwie reparieren, um nicht jedesmal #ifdef zu verwenden?

In C# mag ich Conditional Ich denke, ich brauche so etwas für C++.

+1

Finden Sie eine assert.h - auch die unter http://StackOverflow.com/questions/9701229/c-assert-implementation-in-assert-h für ein Beispiel, wie Sie Code machen, den Sie ausschalten können eine Flagge an einem Ort. –

+2

Sie können die Debug-Methode "define" definieren, um eine ('inline') leere Implementierung im Header zu haben. Verlassen Sie sich dann auf das Optimierungsprogramm, um Funktionsaufrufe zu entfernen, die nichts tun (dies setzt voraus, dass Argumente keine Nebenwirkungen haben). Der traditionellere Weg besteht darin, Präprozessormakros direkt zu verwenden, was direkter ist, aber etwas fehleranfälliger sein kann. – Cameron

+0

Ich gehe davon aus, dass die Argumente auch nicht ausgewertet werden sollen. Dies verkompliziert die Dinge erheblich, wenn Sie ein sauberes C++ Aussehen wünschen. Zeit für einige Makros! –

Antwort

7

Es gibt einen Weg (die Art und Weise llvm), dies mit Makros zu tun.

#ifdef ENABLE_LOGS 
#define DEBUG(ARG) do { ARG; } while(0) 
#else 
#define DEBUG(ARG) 
#endif 

verwenden sie dann als:

DEBUG(logger.Debug("seq=%6d len=%4d", seq, length_);); 
+0

Was ist der Zweck der Do ... während im Makro? –

+3

+ @Monad: Das ist, wenn Sie sagen wollen, wenn (Test) DEBUG (...); sonst ... '. Es macht es zu einer einzigen Aussage. Wenn es auf '{ARG;}' erweitert wird, erhalten Sie einen Syntaxfehler am 'else'. –

+0

Und deshalb ist dieser Ansatz im Allgemeinen schrecklich. –

3

Was ich oft sehen, ist die # define verwenden, um tatsächlich die Log-Anrufe zu definieren, zB:

#define LOG_DEBUG(msg) logger.Debug(msg); 

Aber Sie wollen Umschließen Sie die Definitionen in einem Block, der Ihre Protokollierung aktiviert oder deaktiviert:

#ifdef ENABLE_LOGS 
#define LOG_DEBUG(msg) logger.Debug(msg); 
#else 
#define LOG_DEBUG(msg) 
#endif 

Sie können LOG_DEBUG überall in Ihrem Code aufrufen. Wenn die Protokollierung deaktiviert ist, endet der Aufruf von LOG_DEBUG als leere Zeile in Ihrem endgültigen Code.

+0

Ihre Antwort wird besser, wenn Sie es zu einem [variadic Makro] (http://stackoverflow.com/a/679993/315052) machen. – jxh

+0

Es gibt mehrere Möglichkeiten, sie zu definieren, es ist eine Frage der Präferenz zum größten Teil. Aber das ist eine nette Lösung. – Lochemage

13

Zunächst würde es Sinn machen, einen Blick darauf zu werfen, was schon da draußen ist. Dies ist ein häufiges Problem und viele Leute werden es schon früher gelöst haben. Z. B. siehe stackoverflow question C++ logging framework suggestions und Dr Dobbs A Highly Configurable Logging Framework In C++.

Wenn Sie Ihre eigenen rollen, sollten Sie einige gute Ideen davon erhalten, dies getan zu haben. Es gibt verschiedene Ansätze, die ich in der Vergangenheit angewendet habe. Eine besteht darin, die Anweisung selbst bedingt zu setzen.

#ifdef ENABLE_LOGS 
#define LOG(a,b,c) logger.Debug(a, b, c) 
#else 
#define LOG(a,b,c) 
#endif 

Ein anderer Ansatz besteht darin, die Protokollierungsklasse selbst bedingt zu definieren. Die nicht protokollierende Version enthält alles als leere Anweisungen, und Sie sind darauf angewiesen, dass der Compiler alles optimiert.

#ifdef ENABLE_LOGS 

class Logger 
{ 
public: 
    Logger(std::string name_, std::string fileName, bool append = false); 
    ~Logger(void); 
    void Error(std::string message, ...); 
    void Debug(std::string message, ...); 
    void DebugConsole(std::string message, ...); 
    void Flush(); 
    static Logger errorsLogger; 
    static Logger infoLogger; 
private: 
    FILE* logFile; 
    bool debugEnabled; 
}; 

#else 

class Logger 
{ 
public: 
    Logger(std::string name_, std::string fileName, bool append = false) {} 
    ~Logger(void) {} 
    void Error(std::string message, ...) {} 
    void Debug(std::string message, ...) {} 
    void DebugConsole(std::string message, ...) {} 
    void Flush() {} 
}; 

#endif 

Sie könnten Ihre Logger Implementierung für ENABLE_LOGS in einer CPP-Datei unter der Kontrolle des Makro setzen. Ein Problem bei diesem Ansatz besteht darin, dass Sie sicher sein sollten, die Schnittstelle so zu definieren, dass der Compiler alles optimieren kann. Verwenden Sie z. B. einen C-String-Parametertyp (const char*). In jedem Fall ist const std::string& dem std::string vorzuziehen (letzteres stellt sicher, dass es bei jedem Anruf eine Zeichenkette gibt).

Schließlich, wenn Sie für den ersten Ansatz gehen, sollten Sie alles in do() { ... } while(0) kapseln, um sicherzustellen, dass Sie nicht bizarre Verhalten erhalten, wenn Sie Ihr Makro verwenden, wo eine zusammengesetzte Aussage erwartet werden könnte.

+0

Ich nenne nicht immer Variable 'Logger'. Ich benutze andere Namen und manchmal habe ich mehr als einen Logger sichtbar – javapowered

+0

@javapowered in diesem Fall für den ersten Ansatz könnten Sie einen anderen Parameter für das Makro haben, um welches Logger-Objekt anzugeben. Für den zweiten Ansatz müssen Sie nichts anderes tun - Sie verwenden die Klasse einfach als normal, aber wenn die Protokollierung deaktiviert ist, sind alle Implementierungen inline null und Sie sollten keinen Code in einem Release-Build erhalten. – TooTone

+0

Der zweite Ansatz wird nicht unbedingt alles optimieren. Wenn Sie beispielsweise etwas wie 'debug.Log (..., foo())' haben, wobei 'Log' nichts tut, muss die Funktion' foo' noch aufgerufen werden (es sei denn, sie befindet sich in der gleichen Übersetzungseinheit und hat keine Nebenwirkungen). – Kaz

0

Ein schöner alter Trick ist:

#ifdef ENABLE_LOGS 
#define LOG Logger.Debug 
#else 
#define LOG (void)sizeof 
#endif 

Dann wird der Code:

LOG("seq=%6d len=%4d", seq, length_); 

wird erweitert zu:

Logger.Debug("seq=%6d len=%4d", seq, length_); 

, der das Protokoll der Fall ist. Oder:

(void)sizeof("seq=%6d len=%4d", seq, length_); 

das tut absolut nichts. Es bewertet nicht einmal die Funktionsargumente !!!

Der Trick ist, dass die erste Version das Komma als Argument Trennzeichen in einem Funktionsaufruf verwendet. In der zweiten Version handelt es sich jedoch um einen unbewerteten Komma-Operator.

Einige Compiler können jedoch falsche Warnungen über nicht erreichbaren Code geben.

0

Sie könnten die #ifdef in den Körper der einzelnen Funktionen setzen. Dies vermeidet das Codeduplikationsproblem in TooTones Antwort.

Beispiel:

void fastNative::Logger::Debug(std::string message, ...) 
{ 
#ifdef ENABLE_LOGS 
    // do the actual logging 
#endif 
} 

Wenn ENABLE_LOGS nicht definiert ist, wird diese Funktion nicht alles tun. Was ich vorschlagen würde ist, dass Sie eine anstelle von std::string an diese Methode übergeben. Wenn also ENABLE_LOGS nicht definiert ist, müssen Sie sich nicht auf den Compiler verlassen, um keine redundanten std::string Objekte zu erstellen.