这是我的第一篇文章,所以我想欢迎大家。我遇到的问题是编译时的代码优化,更具体地说是删除调试打印。
让我们假设我们有本地syslog logger
,我们正在包装它(不使用宏,这是非常重要的注意!),代码如下:
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); }
我认为flusher的空实现会鼓励编译器删除这些无用的代码,但是O2
和O3
都不会被删除。
是否有可能引发上述行为?
提前致谢
答案 0 :(得分:1)
我已经成功完成了你正在尝试的东西,虽然至少有两个不同之处...... 1)我没有使用模板 - 这可能会造成编译器无法优化的复杂性,以及2)我的日志使用包含一个宏(见下文)。
此外,您可能已经这样做了,请确保所有“空”定义都在记录器的头文件中(因此优化在编译时完成,而不是推迟到链接时)。
// use it like this
my_log << "info: " << 5 << endl;
发布定义如下所示:
#define my_log if(true);else logger
,调试定义如下所示:
#define my_log if(false);else logger
请注意,编译器会在发行版中优化所有if(true)的记录器,并在调试中使用记录器。另请注意,在两种情况下,完整的if / else语法可以避免使用无范围用途的有趣情况,例如
if (something)
my_log << "this" << endl;
else
somethingelse();
如果没有它,会导致somethingelse
成为my_log的else
。
答案 1 :(得分:0)
所以,按照评论的代码:
inline int f()
{
std::cout << 1;
return 1;
}
需要制作成:
inline int f()
{
#ifndef NDEBUG
std::cout << 1;
#endif
return 1;
}
或类似的东西:
#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;
}
您需要以某种方式告诉编译器不需要此代码。
答案 2 :(得分:0)
您当前的代码并未阻止对f()的调用及其可能产生的任何副作用,仅阻止实际打印。这就是为什么宏是解决这个问题的传统方法 - 它们提供了一个未评估的上下文,您可以在实际打印之前检查是否应该打印该值。
为了在没有宏的情况下实现这一点,需要一些额外的间接,例如, std :: function,函数指针等。举个例子,你可以提供一个包含std :: function的包装类,并专门设置你的流操作符在默认情况下调用std :: function,而不是在NDEBUG情况下调用
非常粗略的例子:
//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) { }
};
然后在您的客户端代码中
int f(){ std::cout << "f()\n"; }
debug_logger log;
log << debug_function(f);
答案 3 :(得分:0)
我用于一些游戏的技术要求调试打印是一个功能而不是一般表达。 E.g:
debug_print("this is an error string: %s", function_that_generates_error_string());
在发布模式下,debug_print
的定义是:
#define debug_print sizeof
删除debug_print
以及从可执行文件传递给它的任何表达式。它仍然必须传递有效的表达式,但它们不会在运行时进行评估。