【问题标题】:Short-circuiting a stream短路流
【发布时间】:2016-09-17 11:21:46
【问题描述】:

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

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

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

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

显而易见的替代方法是在代码中乱扔:

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

它不漂亮,也不舒服。我可以用使用if 的宏替换它,或者使用&amp;&amp; 短路的宏,更好一些(可以用作RValue,遵循Stream 哲学在禁用流上返回bool false):

  #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([&amp;](f){f &lt;&lt; ... ;}); 数百次?

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

【问题讨论】:

  • 我认为闭包是唯一的方法。在引入闭包之前,我必须解决同样的问题,最后我不得不依赖宏和 C 风格的函数调用。
  • “从逻辑上讲,这很好,但在语法上绝对可怕。” 呵呵,欢迎使用 C++
  • @LightnessRacesinOrbit:很高兴我没有使用&lt;:&amp;:&gt; 进行关闭。昨天偶然发现了那个,让我困惑了好一阵子。实际上&lt;:[ 的有向图。在这个时代,谁还在使用二合字母,为什么?以及为什么 C++11 扩展了二合字母和三合字母的集合?!是不是有人误会embedded programming,用带触屏键盘的安卓手机写PC端的应用?
  • @SF.:“为什么 C++11 扩展了二合字母和三合字母的集合”它没有
  • @LightnessRacesinOrbit:哦,等等,它没有添加任何新的。它只是修改了对一个的解释当解析器遇到字符序列<::>时,。跨度>

标签: c++ c++11 conditional iostream


【解决方案1】:

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

第二部分,确保宏主体是单个语句,很简单,通常使用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() 返回false,那么除了检查之外什么都不会发生。

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

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

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

【讨论】:

  • 你可以在没有可变参数宏的情况下解决这个问题。将TRACE 定义为不带参数的宏,其中TRACE 被替换为对函数的调用,该函数返回指向带可变参数的函数的函数指针。根据启用的日志记录,让该函数调用返回两个函数之一。如果启用了日志记录,则返回一个实际记录数据的函数。如果未启用日志记录,则返回一个不做任何事情就直接返回的函数:void ( *traceFunc )( const char *fmt, ... ); 然后#define TRACE traceFunc()。不过不做 C++ IO
  • @AndrewHenle 但是,即使禁用了日志记录,仍然会计算日志消息然后丢弃,这是 OP 希望避免的。
  • @el.pescado 是的。我认为不使用可变参数宏就不可能完全解决问题的这方面。看到这样的解决方案肯定会很有趣。一个纯 C++ IO 解决方案也会很有趣——我的意思是一个根本不需要 C 风格括号的解决方案——它只使用 &lt;&lt; 运算符。
  • @AndrewHenle:我知道一个不使用 ()。它只使用 {}。 try { logger.Trace &lt;&lt; "whatever"; } catch(LoglevelDisabledException ex){}。我想你明白我为什么不建议这样做了 ;)
【解决方案2】:

我解决了这个问题。最后,我用这个接口和相关的宏创建了一个类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) 的解决方案更好,因为我的解决方案后跟; 是两条指令,如果不写在@987654326 之间,就不能在循环或条件中使用@ 和 }。 现在我可以改进我的代码了。 :-)

【讨论】:

    猜你喜欢
    • 2015-11-18
    • 2017-01-20
    • 2016-05-11
    • 2016-06-16
    • 2013-08-23
    • 1970-01-01
    • 2016-10-04
    • 1970-01-01
    • 2012-02-23
    相关资源
    最近更新 更多