之前在看源码的时候,看到下面这个实现:
for (__pp = __cp, void(), __cp = __cp->__next_;
__cp != nullptr;
__cp = __pp->__next_)
是的,for循环中有一个 void(),其在for循环语句中似乎完全没有意义,它什么也不做,没有任何副作用,就那么摆在那里。但,为什么标准里面会写出这种代码呢?
好了,直接给出答案吧:这是针对逗号运算符重载的防御性编程。
众所周知,C++ 支持逗号运算符重载,就像下面这样:
template<typename T, typename U>
T& operator,(T& left, U& right) {
// 其他操作
return left;
}
这种则意味着 a, b 这样的表达式可能不会像“先计算 a,再计算 b”那样运行,相反,它可能会调用一个用户自定义的、行为任意的函数。
好了,接着我们看原始的循环程序:
__pp = __cp,__cp = __cp->__next_
这依赖于 内置的逗号运算符,它保证:
- 从左向右求值
- 顺序执行(左侧完全完成后,右侧才开始)
但如果任一操作数涉及具有重载的用户定义类型 operator,,则:编译器可能会调用该重载函数,而不是使用内置的逗号运算符。这将打破关于求值顺序的假设,这种行为往往是很危险的,尤其是在基础库中。
为了避免上面的问题,就引入了 void():
__pp = __cp, void(), __cp = __cp->__next_
这是因为
The comma operator cannot be overloaded if one of its operands is of type void()
翻译成中文就是:当逗号运算符的任意一个操作数是 void 类型时,逗号运算符不能被重载。
因此,前面的表达式就变成:
(__pp = __cp), void(), (__cp = __cp->__next_)
这样的话,就强制编译器使用内置的逗号表达式,而非用户重载的逗号表达式。
通过插入 void():
- 求值顺序严格从左到右
- 任何用户自定义的
operator, 都不能被执行。
- 行为是确定性的且安全的
这正是标准库所需要的。
当然了,上面只是一个小技巧,还有其他的,比如笔者经常用的 (void)expr; 用来抑制未使用的警告,std::addressof() 避免重载 operator& 等等等等。
这一个小细节,它反映了 C++ 的一个更深层次的真理:编写健壮的通用代码不仅仅是关于应该发生什么——而是关于防范可能发生什么。