Reputation: 71
It is my very first post, so I would like to welcome with everybody. The problem I have occurred is the code optimization at compilation time, and to be more specific removing debug prints.
Let's imagine that we have native syslog logger
and we are wrapping it (without using of macros, it is very important note!) with following code:
enum severity { info_log, debug_log, warning_log, error_log };
template <severity S>
struct flusher {
logger* log_;
flusher(logger* log) : log_(log) {}
flusher(flusher& rhs) : log_(rhs.log_) {}
~flusher() { syslog(S, log_->stream.str()); log_->stream.str(""); }
operator std::ostream& () { return log_->stream; }
};
#ifdef NDEBUG
template <> struct flusher<debug_log> {
flusher(logger*) {}
flusher(flusher&) {}
~flusher() {}
template <typename T> flusher& operator<<(T const&) { return *this; }
};
#endif
struct logger {
std::ostringstream stream;
template <severity T>
flusher<T> operator<<(flusher<T> (*m)(logger&)) { return m(*this); }
};
inline flusher<info_log> info(logger& log) { return flusher<info_log>(&log); }
inline flusher<debug_log> debug(logger& log) { return flusher<debug_log>(&log); }
inline flusher<warning_log> warning(logger& log) { return flusher<warning_log>(&log); }
inline flusher<error_log> error(logger& log) { return flusher<error_log>(&log); }
I thought that the empty implementation of flusher will encourage compiler to remove such useless code, but with both O2
and O3
it is not removed.
Is there any possibility to provoke mentioned behaviour?
Thanks in advance
Upvotes: 1
Views: 2405
Reputation: 54634
The technique I've used for a few games requires the debug printing to be a function rather than a general expression. E.g.:
debug_print("this is an error string: %s", function_that_generates_error_string());
In release mode, the definition of debug_print
is:
#define debug_print sizeof
That removes debug_print
and any expression passed to it from the executable. It still has to be passed valid expressions, but they are not evaluated at runtime.
Upvotes: 0
Reputation: 1316
Your current code is not preventing the call to f() and any side effects it may have, only preventing the actual printing. This is why macros are the traditional approach to this problem - they provide an unevaluated context where you can check if the value should be printed before actually printing.
In order to achieve this without macros, some extra indirection is needed e.g. std::function, function pointers etc. As an example, you could provide a wrapper class which contained a std::function, and specialise your stream operators to call the std::function in the default case, and not in the NDEBUG case
Very rough example:
//Wrapper object for holding std::functions without evaluating
template <typename Func>
struct debug_function_t {
debug_function_t(Func & f) : f(f) {}
decltype(f()) operator()() { return f(); }
std::function<Func> f;
};
//Helper function for type deduction
template <typename Func>
debug_function_t<Func> debug_function(Func & f) {
return debug_function_t<Func>(f);
}
struct debug_logger {
template <typename T>
debug_logger & operator<<(T & rhs) {}
template <typename Func> //Doesn't call f(), so it's never evaluated
debug_logger & operator<<(debug_function_t<Func> f) { }
};
Then in your client code
int f(){ std::cout << "f()\n"; }
debug_logger log;
log << debug_function(f);
Upvotes: 0
Reputation: 5469
I have successfully done what you're attempting, although with at least two differences... 1) I wasn't using templates - that might be creating a complexity the compiler is unable to optimize out, and 2) my log use included a macro (see below).
Additionally, you may have already done this, make sure all your "empty" definitions are in the logger's header file (so optimizations are done at compile-time and not postponed to link-time).
// use it like this
my_log << "info: " << 5 << endl;
The release definition looks like this:
#define my_log if(true);else logger
and the debug definition looks like this:
#define my_log if(false);else logger
Note that the compiler optimizes out the logger for all if(true) in release, and uses the logger in debug. Also note the full if/else syntax in both cases avoids funny situations where you have unscoped use, e.g.
if (something)
my_log << "this" << endl;
else
somethingelse();
would cause somethingelse
to be the else
of my_log without it.
Upvotes: 2
Reputation: 129504
So, following the comment's code:
inline int f()
{
std::cout << 1;
return 1;
}
needs to be made into:
inline int f()
{
#ifndef NDEBUG
std::cout << 1;
#endif
return 1;
}
or something like this:
#ifndef NDEBUG
static const int debug_enable = 1;
#else
static const int debug_enable = 0;
#endif
inline int f()
{
if (debug_enable)
{
std::cout << 1;
}
return 1;
}
You need to tell the compiler somehow that this code isn't needed.
Upvotes: 0