Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Short-circuiting a stream

The application has a logging system, that allows to enable or disable logging features of its modules at runtime. The log commands accept streams on input (being a safe alternative to "sprintf"; there's hardly any more annoying situation than if your debugging system is the cause of crash.

The problem is, that if I perform stuff like:

 logger.Trace << "Requests pending:" << buffer.findRequests();

and findRequests() is of high computational complexity, even if disabling Trace log level for the module, the search will be performed (while assembling the stream) before it's rejected inside the Trace operator<< method.

The obvious alternative would be to litter the code with:

 if(logger.Trace.Enabled()) logger.Trace << ...

It's not pretty and it's not comfortable. I could replace it with a macro using if, or one that uses && short-circuiting, being somewhat nicer (can be used as RValue, which following Stream philosophy returns bool false on disabled stream):

  #define TRACE    if(logger.Trace.Enabled()) logger.Trace

  #define TRACE    dummyLogVar = logger.Trace.Enabled() && logger.Trace

Neither is particularly pretty or safe. A co-worker suggested closures:

  logger.Trace([&](f){f << "Requests pending:" << buffer.findRequests();});

The .Trace would evaluate the closure only if that level is enabled. Logically, that's nice, but syntactically absolutely horrific. Typing that mess: logger.Trace([&](f){f << ... ;}); hundreds of times?

Is there any more neat, safe and comfortable way of preventing evaluation of a stream?

like image 1000
SF. Avatar asked May 20 '16 09:05

SF.


1 Answers

Macros can indeed be used, but you need function-like macros that takes the output as an argument, and you need to make sure it's a single statement.

The second part, making sure the macro body is a single statement, is easy and usually done by using do { ... } while (false).

Using an argument to the macro is not hard either, as long as there's no commas in the argument. The comma-limitation is including using function calls in the macro-argument with its own arguments, the preprocessor is pretty stupid any uses any comma in the macro argument as a separator of arguments for the macro.

In its simplest form, without bothering with the comma-limitation, the macro could then look something like

#define TRACE(output)                   \
    do                                  \
    {                                   \
        if (logger.Trace.Enabled())     \
        {                               \
            logger.Trace << output;     \
        }                               \
     } while (false)

Note that there is no semicilon after the while (false).

You then use it like

TRACE("Requests pending:" << buffer.findRequests());

The do { ... } while (false) part will most likely be optimized away by the compiler, leaving you with a simple if check. If logger.Trace.Enabled() returns false then nothing should happen besides that check.

If you have a compiler capable of C++11 or later it should have support for variadic macros which should help you overcome the limitation about commas in the argument to the macro.

Using variadic macros, the macro instead would look like this:

#define TRACE(...)                       \
    do                                   \
    {                                    \
        if (logger.Trace.Enabled())      \
        {                                \
            logger.Trace << __VA_ARGS__; \
        }                                \
     } while (false)
like image 85
Some programmer dude Avatar answered Oct 22 '22 04:10

Some programmer dude