C语言中如何实现类似Optional的功能?

摘要:Optional 这是一个完整的 optional 类型实现,类似于 C++17 的 std::optional。 整体结构 template<class T>
Optional 这是一个完整的 optional 类型实现,类似于 C++17 的 std::optional。 整体结构 template<class T> struct optional{ private: T value; bool has_value; public: optional(T value) : value(value),has_value(has_value){} T value(){return value;} bool has_value(){return has_value;} }; 上面是一个最简单的optional结构,可以满足基本的判断有无值,取出值等。 但是过于简单,很多都没有考虑到,比如,我们指定了类型T,那么构建otional的时候无论值是否存在,value都会被构造。有着不必要的构造和析构等等... 实现 union包装 既然无论有值与否都会构造析构,那么有没有可以没有值就不构造的方法呢?在C++中,union类型的对象默认不会自动构造和析构函数。因为union可能含有多个成员,而union自己是不知道当前成员是谁的,也就无从谈起自动构造和析构。 union{ T value; }; 这样使用union存储值,可以精确的控制构造和析构时机。 值构造 optional(T &&value) noexcept : _has_value(true) { new (&_value) T(std::move(value)); } optional(T const &value) noexcept : _has_value(true) { new (&_value) T(value); } 我们提供了两种从值构造的方式,分别是右值和左值构造。 重点在于value的构造,union默认开辟最大成员成员所占内存。因为我们这里的union只有一个value,那么所占内存就是T的字节大小。我们要做的就是从已经开辟好的内存直接构造T.使用placement new在union内存构造对象; new (&value) T(value); 在value的内存上,以value(参数value)为值构造union成员value. 空状态构造 我们需要支持当不存在值时的构造。方法是可以先创建一个空状态的辅助类型。 struct nullopt_t { explicit nullopt_t() = default; }; inline constexpr nullopt_t nullopt; 我们这里创建了 nullopt_t 类型,同时添加了全局的 nullopt 对象。这样我们实现一个空构造的函数,只要传入代表空的辅助类型,就说明为 optional 空。 optional(nullopt_t) noexcept : _has_value(false) {} optional() noexcept : optional(nullopt) {} 拷贝/移动 其他的拷贝/移动的构造或者赋值函数形式大差不差,一通百通。 // 拷贝/移动构造 optional(optional const &other) : _has_value(other._has_value) { if (_has_value) { new (&_value) T(other._value); } } optional(optional &&other) noexcept : _has_value(other._has_value) { if (_has_value) { new (&_value) T(std::move(other._value)); } } // 赋值为空 optional &operator=(nullopt_t) noexcept { if (_has_value) { _value.~T(); _has_value = false; } return *this; } // 赋值为值 optional &operator=(T &&value) noexcept { if (_has_value) { this->_value = std::move(value); // 注意:这里有问题! } else { new (&this->_value) T(std::move(value)); _has_value = true; } return *this; } // 拷贝赋值 optional &operator=(optional const &other) { if (this == &other) { return *this; } if (_has_value) { _value.~T(); _has_value = false; } if (other._has_value) { new (&_value) T(other._value); _has_value = true; } return *this; } // 移动赋值 optional &operator=(optional &&other) noexcept { if (this == &other) { return *this; } if (_has_value && other._has_value) { _value = std::move(other._value); // 注意:这里应该是 other._value _has_value = true; } else if (_has_value) { _value.~T(); _has_value = false; } else if (other._has_value) { new (&_value) T(std::move(other._value)); _has_value = true; } other._has_value = false; // 移动后源对象变为空 return *this; } emplace系列 emplace类似标准库的容器的emplace类似函数,支持将参数直接传入就地构造,而不是构造好再传入内部,这样减少了一次移动。 template <class... Args> void emplace(Args &&...args) { if (_has_value) { _value.~T(); _has_value = false; } new (&_value) T(std::forward<Args>(args)...); _has_value = true; } template <class U, class... Args> void emplace(std::initializer_list<U> list, Args &&...args) { if (_has_value) { _value.~T(); _has_value = false; } new (&_value) T(list, std::forward<Args>(args)...); _has_value = true; } reset/析构 销毁值,使 optional 为空。 void reset() noexcept { if (_has_value) { _value.~T(); _has_value = false; } } ~optional() { if (_has_value) { _value.~T(); } } 观察-has_value bool has_value() const noexcept { return _has_value; } explicit operator bool() noexcept { return _has_value; } 检查是否包含值。 值访问-带检查 取值的时候需要判断是否存在,如果不存在抛出异常。 我们这里继承实现一个异常结构。 struct BadOptionalAccess : std::exception { BadOptionalAccess() = default; virtual ~BadOptionalAccess() = default; char const *what() const noexcept override { return "BadOptionalAccess"; } }; 实现value(). T const &value() const & { if (!_has_value) { throw BadOptionalAccess(); } return _value; } T &value() & { if (!_has_value) { throw BadOptionalAccess(); } return _value; } T const &&value() const && { if (!_has_value) { throw BadOptionalAccess(); } return std::move(_value); } T &&value() && { if (!_has_value) { throw BadOptionalAccess(); } return std::move(_value); } operator*/-> - 无检查的访问 T const &operator*() const & noexcept { return _value; } T &operator*() & noexcept { return _value; } T const &&operator*() const && noexcept { return std::move(_value); } T &&operator*() && noexcept { return std::move(_value); } T const *operator->() const noexcept { return &_value; } T *operator->() noexcept { return &_value; } value_or - 提供默认值 T value_or(T &&default_value) const & { return _has_value ? _value : std::move(default_value); } T value_or(T const &default_value) const & { return _has_value ? _value : default_value; } 比较操作符 bool operator==(optional const &other) const noexcept { if (_has_value != other._has_value) { return false; } if (_has_value) { return _value == other._value; } return true; } bool operator!=(optional const &other) const noexcept { if (_has_value != other._has_value) { return true; } if (_has_value) { return _value != other._value; } return false; } bool operator>(optional const &other) const noexcept { if (!_has_value || !other._has_value) { return false; } return _value > other._value; } bool operator<(optional const &other) const noexcept { if (!_has_value || !other._has_value) { return false; } return _value < other._value; } bool operator>=(optional const &other) const noexcept { if (!_has_value || !other._has_value) { return true; } return _value >= other._value; } bool operator<=(optional const &other) const noexcept { if (!_has_value || !other._has_value) { return true; } return _value <= other._value; } bool operator==(nullopt_t) noexcept { return !_has_value; } friend bool operator==(nullopt_t, optional const &self) noexcept { return !self._has_value; } swap 操作 这里使用了using std::swap。默认使用的是标准库的swap进行交换操作。但是如果在传入的other相同的命名空间中也有一个swap函数,那么就会优先使用后者。 void swap(optional &other) noexcept { if (_has_value && other._has_value) { using std::swap; swap(_value, other._value); // 注意:应该是 swap(_value, other._value) } else if (!_has_value && !other._value) { // 都为空,什么都不做 } else if (_has_value) { other.emplace(_value); reset(); } else { emplace(std::move(other._value)); other.reset(); } } make_optional 我们实现make_optional类似智能指针的make_shared,可以根据参数构造optional类型。 因为我们传入的value的类型可能不是单纯的右值或者左值,可能有着引用/const等的修饰,那么对我们容器的T判断就会有问题。我们在返回值类型中使用std::decay_t得出真正的value类型进行存储。对参数进行完美转发。 #if __cpp_deduction_guides template <class T> optional(T) -> optional<T>; // C++17 CTAD #endif template <typename T> optional<std::decay_t<T>> make_optional(T &&value) { return optional<std::decay_t<T>>(std::forward<T>(value)); } template <typename T, typename... Args> optional<T> make_optional(Args &&...args) { return optional<T>(std::forward<Args>(args)...); } 就地构造 比如容器内部存储了一个结构体 struct ss { int a; int b; ss(int a, int b) : a(a), b(b) {} }; optional<ss> o4 我想要构造一个o4,直接传入参数类似emplace初始化构造。 我们为了与普通的T value进行区分,设计了一个inpace_t辅助类型,同时在全局添加了一个为inplace的成员。 重载构造函数第一个参数为inplace_t类型的参数,这样传入的后续参数就会作为内部类型的构造函数参数进行构造value了。 struct inplace_t { explicit inplace_t() = default; }; inline constexpr inplace_t inplace; template <typename... Args> explicit optional(inplace_t, Args &&...args) : _has_value(true) { new (&_value) T(std::forward<Args>(args)...); } template <typename U, class... Args> explicit optional(inplace_t, std::initializer_list<U> list, Args &&...args) : _has_value(true) { new (&_value) T(list, std::forward<Args>(args)...); } 测试代码 #include "optional.hpp" #include <iostream> #include <vector> auto functory(int a) -> optional<int> { if (a > 10) { return optional<int>(a); } else { return nullopt; } } struct ss { int a; int b; ss(int a, int b) : a(a), b(b) {} }; int main(int, char **) { optional<std::vector<int>> o1({1, 2, 3, 4, 54, 6, 7, 8}); if (o1) { for (auto &p: o1.value()) { std::cout << p << " "; } } std::cout << std::endl; auto o2 = make_optional<std::vector<int>>({2, 5, 7, 34, 3, 7, 89, 4, 2}); if (o2) { for (auto &p: o2.value()) { std::cout << p << " "; } } std::cout << std::endl; optional<std::vector<int>> o3({1, 2, 3, 4, 5, 6, 7, 8, 9}); if (o3) { for (auto &p: o3.value()) { std::cout << p << " "; } } optional<ss> o4(inplace, 2, 3); long long int a; while (std::cin >> a) { auto p = functory(a); if (p || p.has_value()) { std::cout << "*p:" << *p << std::endl; std::cout << "p.value():" << p.value() << std::endl; } else { std::cout << "p.has_value:" << p.has_value() << std::endl; std::cout << "p.valur_or(65):" << p.value_or(65) << std::endl; } } } 我们这里进行了多种不同的构造方式以及对成员函数的简单测试,满足我们的需求。 1 2 3 4 54 6 7 8 2 5 7 34 3 7 89 4 2 1 2 3 4 5 6 7 8 9 123123 *p:123123 p.value():123123 12312312321313 p.has_value:0 p.valur_or(65):65 12312312321321 p.has_value:0 p.valur_or(65):65 12312313 *p:12312313 p.value():12312313 源代码 #pragma once #include <exception> #include <initializer_list> #include <type_traits> struct BadOptionalAccess : std::exception { BadOptionalAccess() = default; virtual ~BadOptionalAccess() = default; char const *what() const noexcept override { return "BadOptionalAccess"; } }; struct nullopt_t { explicit nullopt_t() = default; }; inline constexpr nullopt_t nullopt; struct inplace_t { explicit inplace_t() = default; }; inline constexpr inplace_t inplace; template <typename T> struct optional { private: bool _has_value; union { T _value; }; public: optional(T &&value) noexcept : _has_value(true) { new (&_value) T(std::move(value)); } optional(T const &value) noexcept : _has_value(true) { new (&_value) T(value); } optional() noexcept : optional(nullopt) {} optional(nullopt_t) noexcept : _has_value(false) {} template <typename... Args> explicit optional(inplace_t, Args &&...args) : _has_value(true) { new (&_value) T(std::forward<Args>(args)...); } template <typename U, class... Args> explicit optional(inplace_t, std::initializer_list<U> list, Args &&...args) : _has_value(true) { new (&_value) T(list, std::forward<Args>(args)...); } optional(optional const &other) : _has_value(other._has_value) { if (_has_value) { new (&_value) T(other._value); } } optional(optional &&other) noexcept : _has_value(other._has_value) { if (_has_value) { new (&_value) T(std::move(other._value)); } } optional &operator=(nullopt_t) noexcept { if (_has_value) { _value.~T(); _has_value = false; } return *this; } optional &operator=(T &&value) noexcept { if (_has_value) { _value = std::move(value); } else { new (&_value) T(std::move(value)); _has_value = true; } return *this; } optional &operator=(T const &_value) noexcept { if (_has_value) { _value.~T(); _has_value = false; } new (&_value) T(_value); _has_value = true; return *this; } optional &operator=(optional const &other) { if (this == &other) { return *this; } if (_has_value) { _value.~T(); _has_value = false; } if (other._has_value) { new (&_value) T(other._value); } _has_value = other._has_value; return *this; } optional &operator=(optional &&other) noexcept { if (this == &other) { return *this; } if (_has_value && other._has_value) { _value = std::move(other._has_value); _has_value = true; } else if (_has_value) { _value.~T(); _has_value = false; } else if (other._has_value) { new (&_value) T(std::move(other._value)); _has_value = true; } other._has_value = false; return *this; } template <class... Args> void emplace(Args &&...args) { if (_has_value) { _value.~T(); _has_value = false; } new (&_value) T(std::forward<Args>(args)...); _has_value = true; } template <class U, class... Args> void emplace(std::initializer_list<U> list, Args &&...args) { if (_has_value) { _value.~T(); _has_value = false; } new (&_value) T(list, std::forward<Args>(args)...); _has_value = true; } void reset() noexcept { if (_has_value) { _value.~T(); _has_value = false; } } ~optional() { if (_has_value) { _value.~T(); } } bool has_value() const noexcept { return _has_value; } explicit operator bool() noexcept { return _has_value; } bool operator==(nullopt_t) noexcept { return !_has_value; } friend bool operator==(nullopt_t, optional const &self) noexcept { return !self._has_value; } bool operator==(optional const &other) noexcept { if (_has_value != other._has_value) { return false; } return _value == other._value; } bool operator!=(nullopt_t) const noexcept { return _has_value; } friend bool operator!=(nullopt_t, optional const &self) noexcept { return self._has_value; } bool operator!=(optional const &other) noexcept { if (_has_value == other._has_value) { return false; } return _value != other._value; } T const &value() const & { if (!_has_value) { throw BadOptionalAccess(); } return _value; } T &value() & { if (!_has_value) { throw BadOptionalAccess(); } return _value; } T const &&value() const && { if (!_has_value) { throw BadOptionalAccess(); } return std::move(_value); } T &&value() && { if (!_has_value) { throw BadOptionalAccess(); } return std::move(_value); } T const &operator*() const & noexcept { return _value; } T &operator*() & noexcept { return _value; } T const &&operator*() const && noexcept { return std::move(_value); } T &&operator*() && noexcept { return std::move(_value); } T const *operator->() const noexcept { return &_value; } T *operator->() noexcept { return &_value; } T value_or(T &&default_value) const & { return _has_value ? _value : std::move(default_value); } T value_or(T const &default_value) const & { return _has_value ? _value : default_value; } bool operator==(optional<T> const &other) const noexcept { if (_has_value != other._has_value) { return false; } if (_has_value) { return _value == other._value; } return true; } bool operator!=(optional const &other) const noexcept { if (_has_value != other._has_value) { return true; } if (_has_value) { return _value != other._value; } return false; } bool operator>(optional const &other) const noexcept { if (!_has_value || !other._has_value) { return false; } return _value > other._value; } bool operator<(optional const &other) const noexcept { if (!_has_value || !other._has_value) { return false; } return _value < other._value; } bool operator>=(optional const &other) const noexcept { if (!_has_value || !other._has_value) { return true; } return _value >= other._value; } bool operator<=(optional const &other) const noexcept { if (!_has_value || !other._has_value) { return true; } return _value <= other._value; } void swap(optional &other) noexcept { if (_has_value && other._has_value) { using std::swap; swap(_value, other._has_value); } else if (!_has_value && !other._has_value) { // do nothing } else if (_has_value) { other.emplace(_value); reset(); } else { emplace(std::move(other._value)); other.reset(); } } }; #if __cpp_deduction_guides template <class T> // C++17 才有 CTAD optional(T) -> optional<T>; #endif template <typename T> optional<std::decay_t<T>> make_optional(T &&value) { return optional<std::decay_t<T>>(std::forward<T>(value)); } template <typename T, typename... Args> optional<T> make_optional(Args &&...args) { return optional<T>(std::forward<Args>(args)...); }