4

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
Steve Lorimer
  • 27,059
  • 17
  • 118
  • 213
  • Are `LOG_DBG << "Blah"` wrapped with `#ifdef NDEBUG`? If not, that would be a compile error. – Jesse Good May 22 '12 at 02:51
  • Why don't you just check the assembly code generated that would be the best confirmation.Usually, compilers would optimize out creating variables etc if they are not used at all but in this case it is a little difficult to say because `<<` uses method chaining and not sure how intelligently a compiler can handle that.So best is to check the generated assembly code for your compiler. – Alok Save May 22 '12 at 02:53
  • @JesseGood you're right this is not a complete listing - in debug mode the LOG_DBG macro expands into the full logging implementation. I apologise, I thought it would obfuscate the intent of my question – Steve Lorimer May 22 '12 at 02:59
  • @Als, I fear my knowledge of assembly is lacking, which is why I turned to the collective knowledge of stackoverflow – Steve Lorimer May 22 '12 at 03:00
  • You can just make a simple code sample, compile it generate the assembly and check for instructions like `call`, You can actually post the generated assembly output in the Q(along with the code sample) if you can't make sense of it, that way you would get more conclusive answers than mere speculations. – Alok Save May 22 '12 at 03:03
  • Some more discussion [here](http://stackoverflow.com/questions/760301/implementing-a-no-op-stdostream) for more previously collected knowledge on this site :) Just to take one point: the call to obj.get_data() would quite possibly get included. Is obj.get_data const? – azhrei May 22 '12 at 03:04
  • @Als, sorry - what do you mean by "in the Q" ? – Steve Lorimer May 22 '12 at 03:06
  • Woops I mean edit the additional information in the OP(Original Post) – Alok Save May 22 '12 at 03:06
  • @azhrei - thanks for the link - quite possibly the calls are not declared as constant member functions. However, there _should_ be no side-effects to any of the calls, apart from the creation of temporary objects (side-effects implies different behaviour in release-mode vs debug-mode, which is asking for trouble imo) – Steve Lorimer May 22 '12 at 03:14

1 Answers1

4

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.

Community
  • 1
  • 1
Xeo
  • 129,499
  • 52
  • 291
  • 397
  • I attempted to implement this and got the following in release mode: `no match for ‘operator&&’ in ‘false && Logger::operator<<` - did you implement this yourself, and if so, what am I missing please? – Steve Lorimer May 22 '12 at 06:20
  • @lori: Oops, my bad. You either need your `Logger` be convertible to a boolean type (via the safe-bool idiom, for example) or be a bit roundabout. I'll edit that in. – Xeo May 22 '12 at 06:29
  • I see - that's great, thanks! Accepted as answer because it achieves what I was aiming to do – Steve Lorimer May 23 '12 at 00:54