"null logger" - will empty class with ostream operator that does nothing get compiled out of optimised code?

c++, performance

Solution

Not a direct answer to your question, but normally you disable logging in another way: You simply do not evaluate whatever is after `LOG_DBG` through short-circuiting, which is rather simple:

#ifdef NDEBUG
#define LOG_CHECK false &&
#elseif
#define LOG_CHECK /*empty*/

#define LOG_DBG LOG_CHECK /*your_debug_logging_code*/

This way, you guarantee that everything after `LOG_DBG` is dead code that can get optimized out in release mode.

Note that even if they were not compiled out, they wouldn't be executed at runtime thanks to the short-circuiting.

Now, for this to actually work, the `/*your_debug_logging_code*/` needs to evaluate to a boolean value, for example via the safe-bool idiom. The IOstreams for example do that, to let you know if the state of the stream is still OK.

class Logger{
  typedef void (Logger::*safe_bool)();
  void safe_bool_check(){}
public:
  // ...

  operator safe_bool() const{
    return check_state() ? &Logger::safe_bool_check : 0;
  }
};

Of course, if you just want the boolean conversion to make the short-circuiting work, you can simply `return 0;` in the conversion operator.

Note that if you're using a really recent version of GCC or Clang, you might already have access to `explicit` conversion operators, and as such don't need the safe-bool idiom:

explicit operator bool() const{ return check_state(); }

Another way might be to use the ternary operator for the short circuiting. However, both "branches" need to be of the same type, so we'll use a little trick here, a simple class that can be constructed from anything:

namespace log_detail{
struct log_check_helper{
  log_check_helper(){}
  template<class T>
  log_check_helper(T const&){}
};
}

#ifdef NDEBUG
#define LOG_CHECK true ? log_detail::log_check_helper() : 
#else
#define LOG_CHECK /*empty*/
#endif

#define LOG_DBG LOG_CHECK /*your_debug_logging_code*/

With a working example that compiles and does not evaluate the IOstream code on Ideone.

Problem

If I have a logging class which, in release mode, is empty, and has an ostream operator which does nothing. It more-or-less looks like this: ``` struct null_logger { template<typename T> inline null_logger& operator<<(T) { return *this; } }; ``` I've created a simple test and pasted the generated assembly below: ``` const char* foo() { return "hello"; } int main() { int i = 0; null_logger() << i << foo() << " this is a test"; return 0; } ``` To be honest, I don't fully understand the assembly. As per suggestion from @Als, I have looked for `call` statements, of which there are none. Is it therefore safe to assume that, in release mode, any calls to this ostream operator will be compiled out? Here is the generated assembly, using `g++ -O3 -S main.cpp` ``` .file "main.cpp" .section .rodata.str1.1,"aMS",@progbits,1 .LC0: .string "hello" .text .p2align 4,,15 .globl _Z3foov .type _Z3foov, @function _Z3foov: .LFB3: movl $.LC0, %eax ret .LFE3: .size _Z3foov, .-_Z3foov .p2align 4,,15 .globl main .type main, @function main: .LFB4: xorl %eax, %eax ret .LFE4: .size main, .-main .section .eh_frame,"a",@progbits .Lframe1: .long .LECIE1-.LSCIE1 .LSCIE1: .long 0x0 .byte 0x1 .globl __gxx_personality_v0 .string "zPR" .uleb128 0x1 .sleb128 -8 .byte 0x10 .uleb128 0x6 .byte 0x3 .long __gxx_personality_v0 .byte 0x3 .byte 0xc .uleb128 0x7 .uleb128 0x8 .byte 0x90 .uleb128 0x1 .align 8 .LECIE1: .LSFDE1: .long .LEFDE1-.LASFDE1 .LASFDE1: .long .LASFDE1-.Lframe1 .long .LFB3 .long .LFE3-.LFB3 .uleb128 0x0 .align 8 .LEFDE1: .LSFDE3: .long .LEFDE3-.LASFDE3 .LASFDE3: .long .LASFDE3-.Lframe1 .long .LFB4 .long .LFE4-.LFB4 .uleb128 0x0 .align 8 .LEFDE3: .ident "GCC: (SUSE Linux) 4.3.4 [gcc-4_3-branch revision 152973]" .section .comment.SUSE.OPTs,"MS",@progbits,1 .string "Ospwg" .section .note.GNU-stack,"",@progbits ```

Original source

Related problems