1

A common trick I'm aware of to be able to turn off and on logging is to do something like this:

#ifdef DEBUG
    #define debugLog( ... ) printf(__VA_ARGS__)
#else
    #define debugLog( ... )
#endif

This way, a debugLog() statement doesn't do anything at all and produces no compiled code when it is deactivated.

However, I'm writing a C++ app, and the logging mechanism I'm using is in the style of std::cout and requires using the << operator.

Is there a way to make a statement like this:

std::cout << "Some important logging:" << foo << bar;

...become a no-op depending on a processor macro?

I want to keep it a single line for purposes of readability. Something like this:

#ifdef DEBUG
    std::cout << "....";
#endif

is unacceptable if it's littered all over my code. #ifdef statements are quite ugly and break apart clean indentation.

Bri Bri
  • 2,169
  • 3
  • 19
  • 44

2 Answers2

3

Yes, there is a common trick to use a default-initialized std::ofstream. And there are various ways to set it up. A simple approach is just to setup a global variable:

#ifdef DEBUG
std::ostream& debugLog = std::cout;
#else
std::ofstream debugLog;
#endif

And then you use that stream for your logging:

debugLog << "Some important logging:" << foo << bar;

The good thing about your old function-style macro is that if it's a no-op, then none of the arguments will be evaluated either. The entire thing is excluded by the compiler.

By contrast, if you are streaming stuff with this approach, the arguments will (probably) always be evaluated even if the receiving stream is a no-op. That can be important to consider if the stuff you are outputting requires some processing overhead.

This could potentially be addressed by instead using a simple "stream-like" class that the compiler can easily understand does nothing. So, provided any arguments to it are deemed to have no side-effects, then the compiler could reasonably prune them out.

e.g.

struct noopstream
{
    template<class T>
    noopstream& operator<<(const T&)
    {
        return *this;
    }
};

To improve the chances of actually bypassing debug stuff, another trick is this:

#ifdef DEBUG
#define debugLog std::cout
#else
#define debugLog while (0) std::ofstream()
#endif

Now if you use debugLog << foo in a non-debug build, the compiler will not actually evaluate anything because of the while (0).

All this being said, I generally have moved to a non-reliance on this style of debugging. In cases where streams are convenient, I use a #ifdef block and write things into a ostringstream, then pass the resulting string to my debug function.

paddy
  • 60,864
  • 6
  • 61
  • 103
  • When you mention processing overhead, are you talking about the arguments being something like a function call that would need to be evaluated, or are you also talking about the processing overhead of the `<<` operating being evaluated for each argument? – Bri Bri Jul 18 '23 at 23:06
  • 1
    No, I mean the evaluation of the arguments, if the compiler is unable to determine there are no side-effects, or know that the stream will error and do nothing. That itself may be a side-effect. You will have to experiment. You could help the compiler by writing your own pass-through stream-like interface instead, maybe: `struct noopstream { template noopstream& operator<<(const T&) { return *this; } };` – paddy Jul 18 '23 at 23:09
  • I've updated the answer, after recalling a little trick I've used before. – paddy Jul 18 '23 at 23:16
  • I like that little trick. And it works with my personal coding style, which includes _always_ using curly braces after any control statement, for precisely these sorts of reasons. – Bri Bri Jul 19 '23 at 00:40
  • 3
    I think the problem with the "if (0)" repalcement can be fixed if you use "while (0)" instead. – gerum Jul 19 '23 at 08:06
  • That's a good point. Off the top of my head, I can't think of a way that could produce unexpected behavior depending on the surrounding code. – Bri Bri Jul 19 '23 at 13:48
  • Oh, thanks @gerum it seems so obvious, but only after you point it out! ;) The whole approach still feels like a hack, but that's a big improvement. I'll update accordingly. – paddy Jul 19 '23 at 21:53
0

You can set the badbit of the cout:

...

int main()
{
#ifndef DEBUG
    std::cout.setstate(std::ios_base::badbit);
#endif

    std::cout << "Some logging ..." << std::endl;
}

It has some downsides. You can explore alternative options here.

Alireza Roshanzamir
  • 1,165
  • 6
  • 17
  • This has a _lot_ of downsides. By disabling `std::cout` it's rendered useless for anything other than debugging. Even if you were to use `std::cerr` for debugging instead, then the problem remains that you have devoted an entire standard stream to debugging-only, so you couldn't use that for ordinary errors. Note that you've gotten the preprocessor logic backwards too. You meant `#ifndef DEBUG`. – paddy Jul 19 '23 at 22:21
  • @paddy Thanks for your comment. I agree with both of your tips. For the first one, I tried to mention that there are some downsides and give a helpful link. And for the second one, I fixed my answer. – Alireza Roshanzamir Jul 20 '23 at 09:23