1

这是我的第一篇文章,所以我想和大家一起欢迎。我遇到的问题是编译时的代码优化,更具体地说是删除调试打印。

假设我们有 nativesyslog 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被删除。

是否有可能引发上述行为?

提前致谢

4

4 回答 4

2

我已经成功地完成了你正在尝试的事情,尽管至少有两个不同之处...... 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没有它会导致elsemy_log 的。

于 2013-02-06T17:56:40.753 回答
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; 
}

您需要以某种方式告诉编译器不需要此代码。

于 2013-02-06T17:56:20.887 回答
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);
于 2013-02-06T18:05:45.867 回答
0

我在一些游戏中使用的技术要求调试打印是一个函数,而不是一个通用表达式。例如:

debug_print("this is an error string: %s", function_that_generates_error_string());

在发布模式下,定义debug_print为:

#define debug_print sizeof

这将删除debug_print并从可执行文件中传递给它的任何表达式。它仍然必须传递有效的表达式,但它们不会在运行时进行评估。

于 2013-02-06T20:04:12.300 回答