LOG(INFO) << some-exprs
指定 serverity 的 Log 是如何实现的?如果 severity 不足以打印信息,some-exprs
是否会求值导致无效运算?
从 SentencePiece 的代码 common.h/LOG(severity) 中可以看到如下逻辑:
#define LOG(severity) \
(::sentencepiece::logging::GetMinLogLevel() > \
::sentencepiece::logging::LOG_##severity) \
? 0 \
: ::sentencepiece::error::Die( \
::sentencepiece::logging::LOG_##severity >= \
::sentencepiece::logging::LOG_FATAL) & \
std::cerr << ::sentencepiece::logging::BaseName(__FILE__) << "(" \
<< __LINE__ << ") " \
<< "LOG(" << #severity << ") "
- 原来 LOG(severity) 是一个 三元表达式啊!
-
当我们写下
LOG(INFO) << "a";
,宏被扩展后代码是这样的(用clang++ -E xxx
来获取预处理结果,此时宏会被扩展,但还不会被编译)(::sentencepiece::logging::GetMinLogLevel() > ::sentencepiece::logging::LOG_INFO) ? 0 : ::sentencepiece::error::Die( ::sentencepiece::logging::LOG_INFO >= ::sentencepiece::logging::LOG_FATAL) & std::cerr << ::sentencepiece::logging::BaseName("test.cpp") << "(" << 13 << ") " << "LOG(" << "INFO" << ") " << "a";
(为了显示方便,人工增加了换行)
整体上是 (level判断) ? 0 : Die(true/false) & std::cerr « LOG前缀生成 « 用户真正想输出的内容;
如果 level 判断直接失败(低于最低优先级),表达式直接为 0; 否则就执行后面的逻辑:Die 是一个定义的类,如果构造函数参数为true,则析构时调用 Fatal, 这样实现
LOG_FATAL
的功能;后面就是正常的输出了,因为std::cerr <<
op 会返回自己,所以这个前面一堆宏扩展,后面依然可以接自己的逻辑。非常巧妙。 特别地,Die
还实现了&
opint operator&(std::ostream &) { return 0; }
-
仔细思考一下,有两个点要注意:
- 因为
?:
condition operator 的 parse 优先级 (precedence) 比&
,<<
表达式都低,所以:
后面的运算是一个整体,作为:
的一部分被parse到一起。事实上,?:
优先级只比,
高,且这个档位上是右边做左边结合,所以其他的右边的操作都会先结合; 可以说,?:
后面的任何连续(不是用,
连接)表达式,都是优先被 parse 的 - 既然
?:
后面的表达式基本都是先 parse, 那岂不是 true-branch, false-branch 都会先被执行,然后再根据 condition 求值结果来取对应分支的结果?这合理吗?显然,这不合理!我们要知道,parse 是编译器生成代码的前置步骤,具体求值操作的代码,会在 parse 后生成。具体地,按照eval-order 定义,?:
总是从 condition 开始求值的!然后再基于 condition 结果走其中一个 branch,这与我们的直观理解是匹配的。
关于 parse, evalution 以及这二者的区别, stackoverflow-ternary-conditional-and-assignment-operator-precedence 上也有类似的讨论。自己还瞎写了个答案以作补充(花了一个多小时……)
- 因为