每周技巧 #141:小心到 `bool` 的隐式转换
本节阅读量:本文翻译自 Abseil 官网的 Tip of the Week #141: Beware Implicit Conversions to bool。
原文最初作为 TotW #141 发布于 2018 年 1 月 19 日。
更新于 2020 年 4 月 6 日。
快捷链接:abseil.io/tips/141
两种空指针检查
解引用前检查指针是否为空,对于避免崩溃和 bug 很重要。这可以用两种方式完成:
|
|
|
|
当 foo 是指针时,这两个条件语句语义相同,但后者的类型检查稍微更严格。C++ 中许多类型都可以隐式转换为 bool,当被指向的类型本身也能转换为 bool 时,需要额外小心。
考虑下面这段代码,它可能有两种非常不同的含义:
|
|
下面的代码更清楚:
|
|
这两种风格在 Google C++ 代码中都可以接受。因此,当底层类型不能隐式转换为 bool 时,跟随周围代码的风格即可。如果相关值是类似 std::unique_ptr 的“智能指针”,语义和权衡也是一样的。
optional 值和带作用域的赋值
optional(例如 std::optional)值又如何?它们值得更仔细考虑。
例如:
|
|
把变量声明放在 if 语句的条件中,可以限制变量作用域,但这个值会被隐式1转换为 bool,因此被测试的是哪个布尔性质可能并不明确。
下面代码的意图更清楚:
|
|
注意,事实上上面两个代码片段是等价的:std::optional 到 bool 的转换只看这个 optional 是否有值,而不是看其中内容。读者可能会觉得 optional(false) 是 true 反直觉,但 optional(false) 有值这一点立刻就很清楚。再次强调,当底层类型可隐式转换为 bool 时,值得额外小心。
optional 返回值的一种模式,是把变量声明放在 if 语句的条件中。这会限制变量作用域,但涉及到 bool 的隐式转换:
|
|
注意: 在 C++17 中,if 语句可以包含初始化器,因此可以限制声明作用域,同时避免隐式转换:
|
|
“类似布尔”的枚举
假设你采纳了 技巧 #94 的建议,决定在函数签名中使用 enum 而不是 bool,以提升调用点可读性。这种重构可能会在函数定义中引入隐式转换:
|
|
用显式比较替换隐式转换,可以获得额外清晰性:
|
|
总结
总之,要意识到到 bool 的隐式转换可能不清楚,因此可以考虑写得更显式:
- 把指针类型和
nullptr比较(尤其是被指向类型可隐式转换为bool时)。 - 用
std::optional<T>::has_value()这样的具名布尔函数测试 optional 是否有值(尤其是被包含类型可隐式转换为bool时)。使用带 optional 初始化器的if形式来限制变量作用域(技巧 #165)。不过要记得 call-only 接口,不要取得value()或has_value()的地址。测试中可以用testing::Optionalmatcher 帮忙。 - 把枚举和具体值比较。