Oleg Vazhnev
Oleg Vazhnev

Reputation: 24067

easily throw away c++ call completely

I'm trying to implement logging which produce no overhead when not needed (i.e. no method call should be performed at all). I want NO overhead because it's low-latency code. I just added #define ENABLE_LOGS to my header class and now it looks like that (you can ignore details)

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

}

Every time I need to use some method I should surround it like that:

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

It's error-phrone (i can forgot to surround) and makes code dirty. Can I fix my code somehow not to use #ifdef every time?

In C# I like Conditional I guess I need something like that for c++.

Upvotes: 8

Views: 733

Answers (5)

Zeenobit
Zeenobit

Reputation: 5194

You could put the #ifdef inside the body of the individual functions. This avoid the code duplication issue in TooTone's answer.

Example:

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

If ENABLE_LOGS isn't defined, this function doesn't do anything. What I would suggest is you pass a const char* instead of std::string to these method. That way, if ENABLE_LOGS is not defined, you wouldn't have to rely on the compiler to not create redundant std::string objects.

Upvotes: 0

rodrigo
rodrigo

Reputation: 98436

A nice old trick is:

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

Then the code:

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

will expand to:

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

that does the log. Or:

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

that does absolutely nothing. It doesn't even evaluate the function arguments!!!

The trick is that the first version uses the comma as argument separator in a function call. In the second version, however, it is a unevaluated comma operator.

However, some compilers may give spurious warnings about unreachable code.

Upvotes: 0

TooTone
TooTone

Reputation: 8126

First of all it would make sense to have a look to see what's out there already. This is a common problem and many people will have solved it before. E.g., see stackoverflow question C++ logging framework suggestions, and Dr Dobbs A Highly Configurable Logging Framework In C++.

If you do roll your own, you should get some good ideas from having done this. There are several approaches I've used in the past. One is to make the statement itself conditionally defined

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

Another approach is to conditionally define the logging class itself. The non-logging version has everything as empty statements, and you rely on the compiler optimizing everything out.

#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

You could put your Logger implementation for ENABLE_LOGS in a cpp file under control of the macro. One issue with this approach is that you would want to be sure to define the interface so the compiler could optimize everything out. So, e.g., use a C-string parameter type (const char*). In any case const std::string& is preferable to std::string (the latter ensures there's a string copy every time there's a call).

Finally if you go for the first approach, you should encapsulate everything in do() { ... } while(0) in order to ensure that you don't get bizarre behavior when you use your macro where a compound statement might be expected.

Upvotes: 13

Lochemage
Lochemage

Reputation: 3974

What I often see, is to use the #define to actually define the log calls, eg:

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

But you want to wrap the defines in a block that enables or disables your logging:

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

You can call LOG_DEBUG anywhere in your code. If the logging is disabled, calling LOG_DEBUG ends up as a blank line in your final code.

Upvotes: 3

A. K.
A. K.

Reputation: 38216

There is one way (the way llvm does) to do this using macros.

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

Then use it as:

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

Upvotes: 7

Related Questions