1 前言
上一節《Modern C++ std::variant的實現原理》我們簡單分析了std::variant的實現原理,其實要學好C++編程,除了看優秀的代碼包括標準庫實現,讀文檔也是很便捷且必須的一種辦法。
本節我將逐條解析文檔中的五個特性,解析的辦法有兩種:實現代碼講解、用例子舉例。
2 文檔
variant文檔
3解析
以下所有實現代碼都來自/usr/include/c++/11/variant。
3.1 類型安全
The class template std::variant represents a type-safe union.
An instance of std::variant at any given time either holds a value of one of its alternative types, or in the case of error - no value (this state is hard to achieve, see valueless_by_exception).
類型安全,且總會持有一種類型的值,但也有極小的可能無值(valueless)。
無值請參考文檔, 我們重點說下類型安全。
咱們先說下union怎么類型不安全,比如下面的例子:
union Data {int intValue;double doubleValue;};Data d;d.intValue = 10;cout<<d.doubleValue; //類型不安全,存入int,取出double
但variant你做不到這樣:
std::variant<int, double> v;v = 1;cout << get<1>(v)<<endl;
編譯沒問題,但運行報異常:
terminate called after throwing an instance of ‘std::bad_variant_access’
what(): std::get: wrong index for variant
這是因為當前存儲了什么類型是被_M_index記了下來的,在我們的例子中存了int,故_M_index=0, 而double是下一個類型其_M_index=1. 實現代碼如下:
1672 template<size_t _Np, typename... _Types>
1673 constexpr variant_alternative_t<_Np, variant<_Types...>>&
1674 get(variant<_Types...>& __v)
1675 {
1676 static_assert(_Np < sizeof...(_Types),
1677 "The index must be in [0, number of alternatives)");
1678 if (__v.index() != _Np) //index()返回_M_index
1679 __throw_bad_variant_access(__v.valueless_by_exception()); //本例觸發異常
1680 return __detail::__variant::__get<_Np>(__v);
1681 }
當然,類型安全的代價就是需要比union多點內存存_M_index。它的類型可能是char, 也可能是short, 這取決于你的variant聲明時要容納的類型個數:
401 template <typename... _Types>402 using __select_index =403 typename __select_int::_Select_int_base<sizeof...(_Types),404 unsigned char,405 unsigned short>::type::value_type;
446 _Variadic_union<_Types...> _M_u;447 using __index_type = __select_index<_Types...>;448 __index_type _M_index;449 };
3.2 默認持有第一個類型的值
a default-constructed variant holds a value of its first alternative, unless that alternative is not default-constructible
我們先通過一個例子有個直觀的認識:
1 #include <iostream>2 #include <variant>3 #include <string>4 using namespace std;56 int main() {7 class Person{8 public:9 Person(){10 char ch;11 std::cout << "Enter a character: ";12 std::cin.get(ch);13 }14 };15 // Define a variant with 2 alternatives: Person, int16 std::variant<Person,int> vpi; //并沒有顯示指明存入一個Person, 而實際卻是存入了Person
我讓程序卡在輸入那(第12行),方便用GDB看下調用棧
關鍵代碼在棧第14、13層:
704 constexpr705 _Variant_base()706 noexcept(_Traits<_Types...>::_S_nothrow_default_ctor)707 : _Variant_base(in_place_index<0>) { } //類型列表中的第一個類型即Person
**思考:**如果第一個類型沒有默認構造函數哪?
答案也在文檔中
unless that alternative is not default-constructible
把上面的默認構造函數置為delete, 編譯出錯:
還記得上節preview的圖嗎?繼承_Enable_default_constructor的原因也在這里(有機會再細講)
3.3 內存開始即分配好,沒有動態分配內存
As with unions, if a variant holds a value of some object type T, the object representation of T is allocated directly within the object representation of the variant itself. Variant is not allowed to allocate additional (dynamic) memory.
這一點我們上一節已經提到過,不在贅述。
3.4 不能存入引用,數組,void
A variant is not permitted to hold references, arrays, or the type void.
實現代碼有如下片段:
1346 static_assert(sizeof...(_Types) > 0,
1347 "variant must have at least one alternative");
1348 static_assert(!(std::is_reference_v<_Types> || ...),
1349 "variant must have no reference alternative");
1350 static_assert(!(std::is_void_v<_Types> || ...),
1351 "variant must have no void alternative");
顯然引用、void已經被禁。而原生數組不是完整類型,不能在標準庫容器中被用于模板參數。
3.5 可以重復持有相同類型
A variant is permitted to hold the same type more than once, and to hold differently cv-qualified versions of the same type.
可以持有多個相同類型,比如兩個int
1 #include <iostream>2 #include <variant>3 #include <string>4 using namespace std;56 int main() {7 std::variant<int,int> v2i;8 v2i.emplace<0>(1);9 //cout<<get<1>(v2i)<<endl; //雖然類型相同,但依然不能按第二個int取值10 v2i.emplace<1>(2);11 cout<<get<1>(v2i)<<endl;
3.6 get by type
這一條在get部分
獲得數據不僅僅能用下標,還能用類型,比如
std::variant<int, double> v;v = 1;cout << get<double>(v)<<endl;
后臺還是找到double的下標取得數據。如何轉的哪?先不要急,讓我們先看看3.4中提到的例子
7 std::variant<int,int> v2i;8 v2i.emplace<0>(1);9 //cout<<get<1>(v2i)<<endl; //雖然類型相同,但依然不能按第二個int取值10 v2i.emplace<1>(2);11 cout<<get<int>(v2i)<<endl; //get<n> 改為get<int>
這會導致編譯出錯,
顯然它區分不出來你要去第幾個int, 它也不允許這么用:
1116 template<typename _Tp, typename... _Types>
1117 constexpr _Tp& get(variant<_Types...>& __v)
1118 {
1119 static_assert(__detail::__variant::__exactly_once<_Tp, _Types...>,
1120 "T must occur exactly once in alternatives");
1121 static_assert(!is_void_v<_Tp>, "_Tp must not be void");
1122 return std::get<__detail::__variant::__index_of_v<_Tp, _Types...>>(__v);
1123 }
我們例子中_Tp=int, _Types={int,int}, _Tp在_Types中出現了兩次,導致__exactly_once是false, 所以報了1119行的T must occur exactly once in alternatives
__exactly_once的實現又是一個遞歸哈。
721 // For how many times does _Tp appear in _Tuple?722 template<typename _Tp, typename _Tuple>723 struct __tuple_count;724725 template<typename _Tp, typename _Tuple>726 inline constexpr size_t __tuple_count_v =727 __tuple_count<_Tp, _Tuple>::value;728729 template<typename _Tp, typename... _Types>730 struct __tuple_count<_Tp, tuple<_Types...>>731 : integral_constant<size_t, 0> { };732733 template<typename _Tp, typename _First, typename... _Rest>734 struct __tuple_count<_Tp, tuple<_First, _Rest...>>735 : integral_constant<736 size_t,737 __tuple_count_v<_Tp, tuple<_Rest...>> + is_same_v<_Tp, _First>> { };738739 // TODO: Reuse this in <tuple> ?740 template<typename _Tp, typename... _Types>741 inline constexpr bool **__exactly_once** =742 __tuple_count_v<_Tp, tuple<_Types...>> == 1;
回到正常,如果1119行不報錯,則來到
1122 return std::get<__detail::__variant::__index_of_v<_Tp, _Types...>>(__v);
查找_Tp在_Types中的下標,其實現也是遞歸,又是遞歸啊:
167 // Returns the first appearance of _Tp in _Types.168 // Returns sizeof...(_Types) if _Tp is not in _Types.169 template<typename _Tp, typename... _Types>170 struct __index_of : std::integral_constant<size_t, 0> {};171172 template<typename _Tp, typename... _Types>173 inline constexpr size_t __index_of_v = __index_of<_Tp, _Types...>::value;174175 template<typename _Tp, typename _First, typename... _Rest>176 struct __index_of<_Tp, _First, _Rest...> :177 std::integral_constant<size_t, is_same_v<_Tp, _First>178 ? 0 : __index_of_v<_Tp, _Rest...> + 1> {};