std::variant 源码分析
背景:
c++17 里边有个std::variant 它的用法挺神奇的,类似c 语言中的union ,本文将从源码角度,看下支撑它的到底是什么
std::variant 用法
C++ #include #include #include #include int main() { std::variant v, w; v = 42; // v contains int int i = std::get(v); assert(42 == i); // succeeds w = std::get(v); w = std::get<0>(v); // same effect as the previous line w = v; // same effect as the previous line // std::get(v); // error: no double in [int, float] // std::get<3>(v); // error: valid index values are 0 and 1 try { std::get(w); // w contains int, not float: will throw } catch (const std::bad_variant_access& ex) { std::cout << ex.what() << '\n'; } using namespace std::literals; std::variant x("abc"); // converting constructors work when unambiguous x = "def"; // converting assignment also works when unambiguous std::variant y("abc"); // casts to void const * when passed a char const * assert(std::holds_alternative(y)); // succeeds y = "xyz"s; assert(std::holds_alternative(y)); // succeeds } |
Output:
C++ std::get: wrong index for variant |
std::variant 源码分析
C++ template class _LIBCPP_TEMPLATE_VIS variant;
template class _LIBCPP_TEMPLATE_VIS variant : private __sfinae_ctor_base< __all...>::value, __all...>::value>, private __sfinae_assign_base< __all<(is_copy_constructible_v<_Types> && is_copy_assignable_v<_Types>)...>::value, __all<(is_move_constructible_v<_Types> && is_move_assignable_v<_Types>)...>::value> { ...... private: __variant_detail::__impl<_Types...> __impl; } |
这个里边源码极其复杂,这里我们只分析重点, 可以看出来variant内部有个private的成员, __impl,也就搞清楚impl的结构,就搞清楚了整个variant的底层支撑原理
__impl
C++ template class _LIBCPP_TEMPLATE_VIS __impl : public __copy_assignment<__traits<_Types...>> { using __base_type = __copy_assignment<__traits<_Types...>>; { ..... }; |
接下来看下__copy_assignment的定义
__copy_assignment
C++ template class _LIBCPP_TEMPLATE_VIS __copy_assignment;
template \ class _LIBCPP_TEMPLATE_VIS __copy_assignment<__traits<_Types...>, \ copy_assignable_trait> \ : public __move_assignment<__traits<_Types...>> { \ using __base_type = __move_assignment<__traits<_Types...>>; \ \ public: \ using __base_type::__base_type; \ using __base_type::operator=; \ \ __copy_assignment(const __copy_assignment&) = default; \ __copy_assignment(__copy_assignment&&) = default; \ ~__copy_assignment() = default; \ copy_assignment \ __copy_assignment& operator=(__copy_assignment&&) = default; \ } |
__copy_assignment 又继承了__move_assignment
__move_assignment
C++ template class _LIBCPP_TEMPLATE_VIS __move_assignment; template \ class _LIBCPP_TEMPLATE_VIS __move_assignment<__traits<_Types...>, \ move_assignable_trait> \ : public __assignment<__traits<_Types...>> { \ using __base_type = __assignment<__traits<_Types...>>; \ \ public: \ using __base_type::__base_type; \ using __base_type::operator=; \ \ __move_assignment(const __move_assignment&) = default; \ __move_assignment(__move_assignment&&) = default; \ ~__move_assignment() = default; \ __move_assignment& operator=(const __move_assignment&) = default; \ move_assignment \ } |
__move_assignment 又继承了__assignment
__assignment
C++ template class _LIBCPP_TEMPLATE_VIS __assignment : public __copy_constructor<_Traits> { using __base_type = __copy_constructor<_Traits>;
public: using __base_type::__base_type; using __base_type::operator=;
template _Ip, class... _Args> inline _LIBCPP_INLINE_VISIBILITY auto& __emplace(_Args&&... __args) { this->__destroy(); auto& __res = this->__construct_alt(__access::__base::__get_alt<_Ip>(*this), _VSTD::forward<_Args>(__args)...); this->__index = _Ip; return __res; } ....... } |
__assignment 又继承了__copy_constructor
__copy_constructor
C++ emplate class _LIBCPP_TEMPLATE_VIS __copy_constructor;
template \ class _LIBCPP_TEMPLATE_VIS __copy_constructor<__traits<_Types...>, \ copy_constructible_trait> \ : public __move_constructor<__traits<_Types...>> { \ using __base_type = __move_constructor<__traits<_Types...>>; \ \ public: \ using __base_type::__base_type; \ using __base_type::operator=; ....... } |
__copy_constructor 又继承了__move_constructor
__move_constructor
C++ template class _LIBCPP_TEMPLATE_VIS __move_constructor; template \ class _LIBCPP_TEMPLATE_VIS __move_constructor<__traits<_Types...>, \ move_constructible_trait> \ : public __constructor<__traits<_Types...>> { \ using __base_type = __constructor<__traits<_Types...>>; \ \ public: \ using __base_type::__base_type; \ using __base_type::operator=; \ \ __move_constructor(const __move_constructor&) = default; \ move_constructor \ ~__move_constructor() = default; \ __move_constructor& operator=(const __move_constructor&) = default; \ __move_constructor& operator=(__move_constructor&&) = default; \ } |
__move_constructor 又继承了__constructor
__constructor
C++ template class _LIBCPP_TEMPLATE_VIS __constructor : public __destructor<_Traits> { using __base_type = __destructor<_Traits>;
public: using __base_type::__base_type; using __base_type::operator=;
protected: template _Ip, class _Tp, class... _Args> inline _LIBCPP_INLINE_VISIBILITY static _Tp& __construct_alt(__alt<_Ip, _Tp>& __a, _Args&&... __args) { ::new ((void*)_VSTD::addressof(__a)) __alt<_Ip, _Tp>(in_place, _VSTD::forward<_Args>(__args)...); return __a.__value; } .... } |
__constructor 又继承了__destructor
__destructor
C++ template class _LIBCPP_TEMPLATE_VIS __destructor;
template \ class _LIBCPP_TEMPLATE_VIS __destructor<__traits<_Types...>, \ destructible_trait> \ : public __base { \ using __base_type = __base; \ using __index_t = typename __base_type::__index_t; \ \ public: \ using __base_type::__base_type; \ using __base_type::operator=; \ \ __destructor(const __destructor&) = default; \ __destructor(__destructor&&) = default; \ destructor \ __destructor& operator=(const __destructor&) = default; \ __destructor& operator=(__destructor&&) = default; \ \ protected: \ ........ \ } |
__destructor 又继承了__base, 这个__base 是核心关键
__base
C++ template <_Trait _DestructibleTrait, class... _Types> class _LIBCPP_TEMPLATE_VIS __base { public: using __index_t = __variant_index_t;
inline _LIBCPP_INLINE_VISIBILITY explicit constexpr __base(__valueless_t tag) noexcept : __data(tag), __index(__variant_npos<__index_t>) {}
template _Ip, class... _Args> inline _LIBCPP_INLINE_VISIBILITY explicit constexpr __base(in_place_index_t<_Ip>, _Args&&... __args) : __data(in_place_index<_Ip>, _VSTD::forward<_Args>(__args)...), __index(_Ip) {}
inline _LIBCPP_INLINE_VISIBILITY constexpr bool valueless_by_exception() const noexcept { return index() == variant_npos; }
inline _LIBCPP_INLINE_VISIBILITY constexpr size_t index() const noexcept { return __index == __variant_npos<__index_t> ? variant_npos : __index; }
protected: inline _LIBCPP_INLINE_VISIBILITY static constexpr size_t __size() { return sizeof...(_Types); }
__union<_DestructibleTrait, 0, _Types...> __data; __index_t __index;
friend struct __access::__base; friend struct __visitation::__base; }; |
跟了这么久,看了__base 数据结构定义的核心的是
C++ __union<_DestructibleTrait, 0, _Types...> __data; __index_t __index; |
也就是定义了__index,和一个__union,接下来搞清楚这2个,就大概知道其原理了
__index_t
C++ using __index_t = __variant_index_t; template _NumAlts> using __variant_index_t = unsigned int; |
可以理解成 __index_t 就是int, __index_t __index就是 int __index_t
__union
C++ template <_Trait _DestructibleTrait, size_t _Index, class... _Types> union _LIBCPP_TEMPLATE_VIS __union;
template <_Trait _DestructibleTrait, size_t _Index> union _LIBCPP_TEMPLATE_VIS __union<_DestructibleTrait, _Index> {};
template _Index, class _Tp, class... _Types> \ union _LIBCPP_TEMPLATE_VIS __union _Index, \ _Tp, \ _Types...> { \ public: \ ........ \ \ private: \ char __dummy; \ __alt<_Index, _Tp> __head; \ __union __tail; \ \ friend struct __access::__union; \ } |
看的出来__union 底层结构就是union共用体, 而整个结构tuple很相似,就是通过组合方式,拆解可变参数集,利用泛化,版本中指递归,很是巧妙,
总结:
经过上边对variant分析看的出来,内部逻辑十分复杂,最底层支撑的数据就是union,也就是说更高级点。
std::variant 是如何获取取值
我们在用法可以看到std::get<0>(v); 或者std::get(v);是等价的,都可以获取值,我也很好奇,这里边到底是怎么实现的,接下来我们看下源码是怎么实现的,
std::get
C++ template inline constexpr _Tp& get(variant<_Types...>& __v) { return _VSTD::get<__find_exactly_one_t<_Tp, _Types...>::value>(__v); } |
可以看的出来,_Tp是int
__find_exactly_one_t
C++ template struct __find_exactly_one_t : public __find_detail::__find_exactly_one_checked<_T1, _Args...> { }; template struct __find_exactly_one_checked { static constexpr bool __matches[sizeof...(_Args)] = {is_same<_T1, _Args>::value...}; static constexpr size_t value = __find_detail::__find_idx(0, __matches); static_assert(value != __not_found, "type not found in type list" ); static_assert(value != __ambiguous, "type occurs more than once in type list"); }; |
上边源码看到__find_exactly_one_t<_Tp, _Types...>::value 是找到你要get类型,在union的位置,然好调用std::get<0>(v),也就是你根据类型获取值,最终转化成了根据序号获取值
__matches[sizeof...(_Args)] = {is_same<_T1, _Args>::value...}; 是解包操作,存储了共用体的类型与你要的类型相比匹配的值
__find_idx 找到__matches里边为true的值,它的index就是对应的序号
std::get<0>
C++ template _Ip, class... _Types> inline _LIBCPP_INLINE_VISIBILITY _LIBCPP_AVAILABILITY_THROW_BAD_VARIANT_ACCESS constexpr variant_alternative_t<_Ip, variant<_Types...>>& get( variant<_Types...>& __v) { return __generic_get<_Ip>(__v); } |
这个要比上边定义复杂一些,这里我们就不具体跟源码,variant_alternative_t<_Ip, variant<_Types...>>拿到的是,0号元素对应类型,
__generic_get
C++ template _Ip, class _Vp> inline _LIBCPP_INLINE_VISIBILITY constexpr auto&& __generic_get(_Vp&& __v) { using __variant_detail::__access::__variant; if (!__holds_alternative<_Ip>(__v)) { __throw_bad_variant_access(); } return __variant::__get_alt<_Ip>(_VSTD::forward<_Vp>(__v)).__value; } template _Ip, class... _Types> inline _LIBCPP_INLINE_VISIBILITY constexpr bool __holds_alternative(const variant<_Types...>& __v) noexcept { return __v.index() == _Ip; } |
__holds_alternative<_Ip>(__v) 用于判断当前取的类型,跟variant初始化的时候类型是否一致,如果不一致会抛异常,这也就解释了用法中
C++ try { std::get(w); // w contains int, not float: will throw } catch (const std::bad_variant_access& ex) { std::cout << ex.what() << '\n'; } |
会抛异常的根本原因
__get_alt
C++ struct __variant { template _Ip, class _Vp> inline _LIBCPP_INLINE_VISIBILITY static constexpr auto&& __get_alt(_Vp&& __v) { return __base::__get_alt<_Ip>(_VSTD::forward<_Vp>(__v).__impl); } };
struct __base { template _Ip, class _Vp> inline _LIBCPP_INLINE_VISIBILITY static constexpr auto&& __get_alt(_Vp&& __v) { return __union::__get_alt(_VSTD::forward<_Vp>(__v).__data, in_place_index<_Ip>); } };
struct __union { template inline _LIBCPP_INLINE_VISIBILITY static constexpr auto&& __get_alt(_Vp&& __v, in_place_index_t<0>) { return _VSTD::forward<_Vp>(__v).__head; }
template _Ip> inline _LIBCPP_INLINE_VISIBILITY static constexpr auto&& __get_alt(_Vp&& __v, in_place_index_t<_Ip>) { return __get_alt(_VSTD::forward<_Vp>(__v).__tail, in_place_index<_Ip - 1>); } }; |
经过上边一层一层的着最终到了struct __union 这个结构挺有意思的, 它也是递归解包,直到匹配到了__get_alt 的上边一个版本, 然后返回__head,
总结
通过分析源码可以看出来__variant::__get_alt<_Ip>(_VSTD::forward<_Vp>(__v)).__value 这个最终返回了std::variant里边底层结构union对应位置的值,很是不好分析,希望对读者有所帮助