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)...);
}
