采用这个微不足道的 class 来处理可选值可能会出什么问题?

What could possibly go wrong adopting this trivial class to handle optional values?

在某些应用程序中,我受困于 c++98 编译器。 我喜欢处理返回可选错误的想法,所以我设计了这个简单的 class 来处理它们:

template<class T> class Optional
{
 public:
    Optional<T>() : i_Assigned(false) {}
    explicit Optional<T>(const T& v) : i_Value(v), i_Assigned(true) {}

    bool assigned() const { return i_Assigned; }
    const T& value() const { if(!i_Assigned) throw std::runtime_error("Optional value not assigned!"); return i_Value; }

    Optional<T>& operator=(const T& v) { i_Value=v; i_Assigned=true; return *this; }

 private:
    T i_Value;
    bool i_Assigned;
};

到目前为止,我对我的测试很满意,我即将在我的生产代码中使用它:

Optional<double> val = funct_returning_optional();
if( val.assigned() ) use(val.value());
else something_went_wrong();

...但它的极端简单让我想知道,我错过了什么?为什么标准实施如此复杂?采用我自制的可憎物品可能会出什么问题?

您的实现的一个基本问题是它无法适应缺少默认构造函数的类型。同样,如果 T 的默认构造函数有任何副作用,那么在构造 Optional<T> 时也会产生这些副作用。 std::optional<T> 在默认构造时不应该有任何副作用(事实上,默认构造函数是 constexpr)。

std::optional 处理此问题的方法是不使用 T 类型的成员,而是使用一个联合成员,其中一个元素的类型为 T:

template <class T>
class optional {
    union Storage {
        T t;
        char c;
        Storage() : c(0) {}
    } storage;
    bool has_value;
  public:
    optional() : has_value(false) {}
};

默认构造可选对象时,初始化了storage.c成员,还没有T对象,所以T不需要调用构造函数。

这立即使实施的其余部分复杂化。每当可选项从具有 T 变为不具有 T 时(当它被销毁、重置或从空可选项分配时可能发生),必须在 [=23 上显式调用析构函数=].每当它从没有 T 到有 T 时(当它是从非空可选的复制构造,从 T 分配或从非空分配时可能发生-empty optional), placement new 必须用于开始 storage.t.

的生命周期

C++17 optional 还有其他一些关键要求。我假设您已经了解移动构造函数和移动赋值运算符。然而,真正使事情复杂化的是,只要 T 的相应成员是微不足道的,std::optional<T> 就需要有一个微不足道的复制构造函数、复制赋值运算符、移动构造函数、移动赋值运算符或析构函数。在 C++17 中,实现此要求的唯一方法是让 optional<T> 从内部基类 class 中引入其特殊成员函数,其中一个版本的基类 class 处理通过使用隐式定义的函数处理普通情况,另一个版本使用用户提供的函数处理非普通情况。这就是标准库实现这么长的主要原因。