短路流

Short-circuiting a stream

该应用程序有一个日志系统,允许在运行时启用或禁用其模块的日志记录功能。日志命令接受输入流(作为 "sprintf" 的安全替代方案;几乎没有比调试系统导致崩溃更烦人的情况了。

问题是,如果我执行以下操作:

 logger.Trace << "Requests pending:" << buffer.findRequests();

findRequests()具有很高的计算复杂度,即使禁用模块的Trace日志级别,也会在Trace operator<<方法中被拒绝之前执行搜索(在组装流时) .

明显的替代方法是在代码中乱加:

 if(logger.Trace.Enabled()) logger.Trace << ...

不漂亮,也不舒服。我可以使用 if 的宏或使用 && 短路的宏来替换它,这样会更好一些(可以用作 RValue,遵循 Stream 哲学 returns bool false on disabled流):

  #define TRACE    if(logger.Trace.Enabled()) logger.Trace

  #define TRACE    dummyLogVar = logger.Trace.Enabled() && logger.Trace

都不是特别漂亮或安全。一位同事建议关闭:

  logger.Trace([&](f){f << "Requests pending:" << buffer.findRequests();});

.Trace 仅在启用该级别时才会评估闭包。从逻辑上讲,这很好,但在语法上绝对可怕。乱七八糟的输入:logger.Trace([&](f){f << ... ;}); 数百次?

有没有更简洁、安全和舒适的方法来防止对流进行评估?

确实可以使用宏,但是你需要类似函数的宏,将输出作为参数,并且你需要确保它是单个语句。

第二部分,确保宏体是单个语句,很简单,通常使用 do { ... } while (false)

使用宏的参数也不难,只要参数中没有逗号即可。逗号限制包括在宏参数中使用带有自己参数的函数调用,预处理器非常愚蠢,在宏参数中使用 any 逗号作为宏参数的分隔符.

在不考虑逗号限制的最简单形式中,宏可能看起来像

#define TRACE(output)                   \
    do                                  \
    {                                   \
        if (logger.Trace.Enabled())     \
        {                               \
            logger.Trace << output;     \
        }                               \
     } while (false)

注意while (false)后没有分号。

然后你就可以像这样使用它了

TRACE("Requests pending:" << buffer.findRequests());

do { ... } while (false) 部分很可能会被编译器优化掉,只剩下一个简单的 if 检查。如果 logger.Trace.Enabled() returns false 那么除了检查之外什么都不应该发生。

如果您的编译器支持 C++11 或更高版本,它应该支持 variadic macros,这应该可以帮助您克服宏参数中逗号的限制。

使用可变宏,宏将如下所示:

#define TRACE(...)                       \
    do                                   \
    {                                    \
        if (logger.Trace.Enabled())      \
        {                                \
            logger.Trace << __VA_ARGS__; \
        }                                \
     } while (false)

我解决了那个问题。最后,我用这个接口和相关的宏创建了一个 class Log :

class Log
{
  public:
    static bool trace_is_active();
    static bool debug_is_active();
    static bool info_is_active();
    static bool warning_is_active();
    static bool error_is_active();
    static void write_as_trace(const std::string& msg);
    static void write_as_debug(const std::string& msg);
    static void write_as_info(const std::string& msg);
    static void write_as_warning(const std::string& msg);
    static void write_as_error(const std::string& msg);
};

#define LOG_TRACE(X) {if(Log::trace_is_active()){std::ostringstream o__;o__<<X;Log::write_as_trace(o__.str());}}
#define LOG_DEBUG(X) {if(Log::debug_is_active()){std::ostringstream o__;o__<<X;Log::write_as_debug(o__.str());}}
#define LOG_INFO(X) {if(Log::info_is_active()){std::ostringstream o__;o__<<X;Log::write_as_info(o__.str());}}
#define LOG_WARNING(X) {if(Log::warning_is_active()){std::ostringstream o__;o__<<X;Log::write_as_warning(o__.str());}}
#define LOG_ERROR(X) {if(Log::error_is_active()){std::ostringstream o__;o__<<X;Log::write_as_error(o__.str());}}

那么用法就很简单明了:

//...
LOG_WARNING("The variable x = " << x << " is out of range");
//...
LOG_DEBUG("The inverse of the matrix is inv(m) = " << std::endl << inv(m) << std::endl);

编辑: 我注意到 do{...}while(false) 的解决方案更好,因为我的解决方案后跟 ; 是两条指令,不能用于循环或条件而不在 {} 之间写入它。 现在我可以改进我的代码了。 :-)