optional原理
Mozart的optional也是一个容器,负责管理一个可选的容纳值,这个容纳值可以存在,也可以不存在。文件git地址:https://github.com/libmozart/foundation/blob/master/mozart%2B%2B/mpp_foundation/optional.hpp
其实现原理:optional所持有的数据除容纳值外,还专门添加布尔标志,标记当前optional是否真是存在。因此,optional大小通常比包含的对象大一个字节,参见optional的内部数据结构定义:
std::array<unsigned char, 1 + sizeof(T)> _memory{0};
第一个字节memory[0]存储容纳值的状态;如果memory[0]==true,表示optional存在一个数据对象,memory[0] == false表示不存在一个数据对象。
第一个字节以外的其他部分memory[1, …, 1+sizeof(T)]存储的是原始数据对象。
optional构造
optional的构造函数包括默认构造函数,空构造函数,隐式(移动)构造,列表初始化构造,多实参初始化构造等多个形式。
默认构造
optional() = default;
optional空构造
optional空构造仅用于空对象的生成,optional空对象仅仅是一个标识说明,表示当前optional未包含任何对象。
// 定义一个optional空对象
struct optional_none_t {};
static constexpr optional_none_t none {};
optional于此对应的空构造函数为:
optional(optional_none_t) : optional()
{}
optional隐式构造
隐式构造实现基于原始对象生成optional对象,隐式构造包括两种:一种为基于左值的隐式构造,一种为基于右值的移动隐式构造。
基于左值的隐式构造:
optional(const T &t)
{
new(_memory.data() + 1) T(t);
_memory[0] = static_cast<unsigned char>(true);
}
基于右值的移动隐式构造
optional(T &&t)
{
new(_memory.data() + 1) T(std::forward<T>(t));
_memory[0] = static_cast<unsigned char>(true);
}
option拷贝构造
optional的拷贝构造包括左值拷贝构造和右值拷贝构造两种。
左值拷贝
左值拷贝构造为传统意义上C++98支持的拷贝构造方式。定义如下:
optional(const optional<T> &other)
{
if (other.has_value())
{
new(_memory.data() + 1) T(other.get());
_memory[0] = static_cast<unsigned char>(true);
}
}
右值拷贝
右值拷贝为C++11支持的基于移动语义的拷贝构造,定位如下:
// 实现当前对象_memory和other的_memory数据交换
void swap(optional<T> &&other)
{
std::swap(this->_memory, other._memory);
}
optional(optional<T> &&other) noexcept
{
swap(other);
}
optional转发构造
optional的其他构造方法,都采用some中介转发实现。我们首先介绍some的in_place匹配引导机制,然后介绍some中介转发构造机制。
in_place
in_place机制其实就是一种函数定义匹配机制。
#define mpp_in_place_type_t(T) mpp_impl::in_place_t(&)(mpp_impl::in_place_type_tag<T>)
template <typename T>
struct in_place_type_tag
{
};
struct in_place_t
{
};
宏mpp_in_place_type_t就是一个函数声明定义,这个函数入参为mpp_impl::in_place_type_tag,返回值为in_place_t。
template <typename T>
inline in_place_t in_place(in_place_type_tag<T> = in_place_type_tag<T>())
{
return in_place_t{};
}
inline in_place_t in_place(in_place_type_tag = in_place_type_tag()) 函数定义就是满足宏mpp_in_place_type_t声明函数的一个匹配实现。
some转发机制
简单的说some转发就是通过optional模块提供的some函数实现optional对象构造,optional模块提供的some函数包括三种,他们分别是单对象some转发,入参列表形式转发,列表初始化转发。
单对象some
optional<typename std::decay::type> some(T &&v)实现单对象右值赋值构造初始化工作。
template <typename T>
constexpr optional<typename std::decay<T>::type> some(T &&v)
{
return optional<typename std::decay<T>::type>(std::forward<T>(v));
}
调用方式为:
auto optionalInt = mpp::some(int(1));
这种定义初始化就会调用此some转发初始化构造函数。
入参列表形式some
此转发构造方式赋值适配c++98标准老式初始化方式,调用方式为:
auto optionalVect98 = mpp::some<std::vector<int>>(1, 2, 3, 4, 5)));
some转发构造函数定义:
template <typename T, typename... Args>
constexpr optional<T> some(Args &&... args)
{
return optional<T>(mpp_impl::in_place, std::forward<Args>(args)...);
}
mpp_impl::in_place是一个函数对象,通过此函数对象,编译器会匹配到optional的optional(mpp_in_place_type_t(T), Args &&… args)构造函数。
template <typename... Args>
constexpr explicit optional(mpp_in_place_type_t(T), Args &&... args)
: optional(T{std::forward<Args>(args)...})
{}
列表初始化some
此转发构造方式赋值适配c++11标准新式初始化方式,调用方式为:
auto optionalVect11 = mpp::some<decltype(v)>({ 1, 2, 3, 4, 5 }));
some转发构造函数定义:
template <typename T, typename U, typename... Args>
constexpr optional<T> some(std::initializer_list<U> il, Args &&... args)
{
return optional<T>(mpp_impl::in_place, il, std::forward<Args>(args)...);
}
mpp_impl::in_place是一个函数对象,通过此函数对象,编译器会匹配到optional的optional(mpp_in_place_type_t(T), std::initializer_list il, Args &&… args)构造函数。
template <typename U, typename... Args>
constexpr explicit optional(mpp_in_place_type_t(T), std::initializer_list<U> il, Args &&... args)
: optional(T{il, std::forward<Args>(args)...})
{}
optional析构
optional析构实现optional数据的释放和回收,通过定位析构实现对象所管理资源的回收。
T *ptr()
{
return has_value() ? reinterpret_cast<T *>(_memory.data() + 1) : nullptr;
}
const T *ptr() const
{
return has_value() ? reinterpret_cast<const T *>(_memory.data() + 1) : nullptr;
}
~optional()
{
if (has_value())
{
ptr()->~T();
_memory[0] = static_cast<unsigned char>(false);
}
}
析构函数~optional()通过ptr()函数获取所持有对象的指针,然后采用定位析构的方式回收资源。
此处需要重点说明的是:为何采用定位析构,而不是通过delete ptr()这种方式?ptr所指向对象在函数调用栈上,对应地址为_memory.data() + 1,而不在堆heap。所以在这儿的回收,只能回收数据,不能把对象交换操作系统,而定位析构专门是为解决这个问题的,这就是不能直接调用delete ptr()回收资源的原因。
optional赋值
同样赋值也存在两种,分别是左值赋值,右值赋值。
左值赋值
左值赋值通过std::swap数据交换赋值操作。
void swap(optional<T> &other)
{
std::swap(this->_memory, other._memory);
}
optional &operator=(const optional<T> &other)
{
if (this == &other)
{
return *this;
}
optional<T> t(other);
swap(t);
return *this;
}
右值赋值
右值赋值通过std::swap数据交换赋值操作。
void swap(optional<T> &&other)
{
std::swap(this->_memory, other._memory);
}
optional &operator=(optional<T> &&other) noexcept
{
if (this == &other)
{
return *this;
}
swap(other);
return *this;
}
get_or
获取原始get_or,根据调用对象是否为const对象,可分为const版本和分const版本。分别定义如下:
// 非const版本get_or
template <typename U>
std::enable_if_t<std::is_same<T, U>::value, T> &get_or(U &&o)
{
if (ptr() == nullptr)
{
return o;
}
return *ptr();
}
// const版本get_or
template <typename U>
const std::enable_if_t<std::is_same<T, U>::value, T> &get_or(U &&o) const
{
if (ptr() == nullptr)
{
return o;
}
return *ptr();
}
std::enable_if_t<std::is_same<T, U>::value, T> 用于限制U和T必须是同一类型的数据,否则会报出编译错误。
apply/apply_or
apply和apply_or与get_or实现方式存在异曲同工之处,大家可以自行分析,这儿贴出其定义:
void apply(const std::function<void(T &)> &consumer)
{
if (ptr() != nullptr)
{
consumer(get());
}
}
void apply(const std::function<void(const T &)> &consumer) const
{
if (ptr() != nullptr)
{
consumer(get());
}
}
template <typename R>
R apply_or(const R &r, const std::function<R(T &)> &consumer)
{
if (ptr() != nullptr)
{
return consumer(get());
}
return r;
}
template <typename R>
R apply_or(const R &r, const std::function<R(const T &)> &consumer) const
{
if (ptr() != nullptr)
{
return consumer(get());
}
return r;
}
总结
本文详细介绍了,optional类的数据存储和接口。希望可以对你有所帮助。