我们之前已经讨论了异常是推荐的 C++ 错误处理方式。不过,C++ 里有另外一些结构也很适合进行错误处理,今天我们就来讨论一下。
optional
在面向对象(引用语义)的语言里,我们有时候会使用空值 null 表示没有找到需要的对象。也有人推荐使用一个特殊的空对象,来避免空值带来的一些问题 [1]。可不管是空值,还是空对象,对于一个返回普通对象(值语义)的 C++ 函数都是不适用的——空值和空对象只能用在返回引用 / 指针的场合,一般情况下需要堆内存分配,在 C++ 里会引致额外的开销。
C++17 引入的 optional 模板 [2] 可以(部分)解决这个问题。语义上来说,optional 代表一个“也许有效”“可选”的对象。语法上来说,一个 optional 对象有点像一个指针,但它所管理的对象是直接放在 optional 里的,没有额外的内存分配。
构造一个 optional<T> 对象有以下几种方法:
不传递任何参数,或者使用特殊参数 std::nullopt(可以和 nullptr 类比),可以构造一个“空”的 optional 对象,里面不包含有效值。
第一个参数是 std::in_place,后面跟构造 T 所需的参数,可以在 optional 对象上直接构造出 T 的有效值。
如果 T 类型支持拷贝构造或者移动构造的话,那在构造 optional<T> 时也可以传递一个 T 的左值或右值来将 T 对象拷贝或移动到 optional 中。
对于上面的第 1 种情况,optional 对象里是没有值的,在布尔值上下文里,会得到 false(类似于空指针的行为)。对于上面的第 2、3 两种情况,optional 对象里是有值的,在布尔值上下文里,会得到 true(类似于有效指针的行为)。类似的,在 optional 对象有值的情况下,你可以用 * 和 -> 运算符去解引用(没值的情况下,结果是未定义行为)。
虽然 optional 是 C++17 才标准化的,但实际上这个用法更早就通行了。因为 optional 的实现不算复杂,有些库里就自己实现了一个版本。比如 cpptoml [3] 就给出了下面这样的示例(进行了翻译和重排版),用法跟标准的 optional 完全吻合:
auto val = config->
get_as<int64_t>("my-int");
// val 是 cpptoml::option<int64_t>
if (val) {
// *val 是 "my-int" 键下的整数值
} else {
// "my-int" 不存在或不是整数
}
cpptoml 里只是个缩微版的 optional,实现只有几十行,也不支持我们上面说的所有构造方式。标准库的 optional 为了方便程序员使用,除了我目前描述的功能,还支持下面的操作:
安全的析构行为
显式的 has_value 成员函数,判断 optional 是否有值
value 成员函数,行为类似于 *,但在 optional 对象无值时会抛出异常 std::bad_optional_access
value_or 成员函数,在 optional 对象无值时返回传入的参数
swap 成员函数,和另外一个 optional 对象进行交换
reset 成员函数,清除 optional 对象包含的值
emplace 成员函数,在 optional 对象上构造一个新的值(不管成功与否,原值会被丢弃)
make_optional 全局函数,产生一个 optional 对象(类似 make_pair、make_unique 等)
全局比较操作
等等
如果我们认为无值就是数据无效,应当跳过剩下的处理,我们可以写出下面这样的高阶函数:
template <typename T>
constexpr bool has_value(
const optional<T>& x) noexcept
{
return x.has_value();
}
template <typename T,
typename... Args>
constexpr bool has_value(
const optional<T>& first,
const optional<
Args>&... other) noexcept
{
return first.has_value() &&
has_value(other...);
}
template <typename F>
auto lift_optional(F&& f)
{
return [f = forward<F>(f)](
auto&&... args) {
typedef decay_t<decltype(f(
forward<decltype(args)>(args)
.value()...))>
result_type;
if (has_value(args...)) {
return optio