还搞不懂STL的type_traits?从源码来带你一起分析
文章目錄
- 什么是類型萃取?
- 源碼剖析
- SGI-STL G2.9版本類型萃取
- C++標準庫類型萃取
- SFINAE機制與enable_if
- conditional
- 核心結構:integral_constant 與bool_constant
- is_same
- 類型轉換:remove/add_xx()
- 實戰分析:is_void
什么是類型萃取?
type_traits被稱為類型萃取,主要用于在編譯期計算、查詢、判斷、轉換和選擇,增強了泛型編程的能力,也增強了程序的彈性,使得我們在編譯期就能做到優化改進甚至排錯,能進一步提高代碼質量。
cplusplus-type_traits
在C++中類型萃取主要包含三大部分
-
輔助類(Helper classes)
- 用于幫助創建編譯器常量的標準類。
- 用于幫助創建編譯器常量的標準類。
-
類型萃取(Type traits)
- 在編譯期以常量的形式獲取類的特征。
- 在編譯期以常量的形式獲取類的特征。
-
類型轉換(Type transformations)
- 為某個類型增加/刪除屬性。例如增加/刪除const、volatile等。
- 為某個類型增加/刪除屬性。例如增加/刪除const、volatile等。
源碼剖析
以下代碼全部來自C++標準庫的type_traits、xtr1common,SGI-STL的type_traits.h文件中,為了方便閱讀與理解,在一些重要的地方我會加上注釋。
SGI-STL G2.9版本類型萃取
考慮到理解難度,我們先不講標準庫的類型萃取,先看看G2.9的STL中單獨封裝的類型萃取。
在SGI-STL的類型萃取中,每個類型其主要關注六個參數
- this_dummy_member_must_be_first:這個虛函數成員必須為第一個參數
- has_trivial_default_constructor:默認的構造函數是否不重要。
- has_trivial_copy_constructor:拷貝構造函數是否不重要。
- has_trivial_assignment_operator:賦值運算符是否不重要。
- has_trivial_destructor:析構函數是否不重要。
- is_POD_type:是否為基本類型。
為什么要選擇這些參數呢?例如我們在使用一個容器時(例如vector<type>),如果其為自定義類型,我們需要在構造的時候為每一個成員調用一次構造函數,在析構的時候調用一次析構函數。但是對于內置類型來說,這些操作完全不必要,我們可以直接采用內存直接處理操作如malloc()、memcpy()等方法來以最高效率執行。
如果我們準備對一個類型未知的數組進行拷貝操作時,如果能夠提前知道它使用的是默認的拷貝構造函數,就可以直接使用memcpy、memmove來快速進行拷貝,對于STL中這些大規模且操作頻繁的容器,帶來了巨大的性能提升。
下面結合源碼,來看看它們是怎么實現的
struct __true_type { };struct __false_type { };//泛化模板 template <class type> struct __type_traits { typedef __true_type this_dummy_member_must_be_first;typedef __false_type has_trivial_default_constructor;typedef __false_type has_trivial_copy_constructor;typedef __false_type has_trivial_assignment_operator;typedef __false_type has_trivial_destructor;typedef __false_type is_POD_type; };//特化,太多了這里只舉幾個例子 __STL_TEMPLATE_NULL struct __type_traits<char> {typedef __true_type has_trivial_default_constructor;typedef __true_type has_trivial_copy_constructor;typedef __true_type has_trivial_assignment_operator;typedef __true_type has_trivial_destructor;typedef __true_type is_POD_type; };__STL_TEMPLATE_NULL struct __type_traits<int> {typedef __true_type has_trivial_default_constructor;typedef __true_type has_trivial_copy_constructor;typedef __true_type has_trivial_assignment_operator;typedef __true_type has_trivial_destructor;typedef __true_type is_POD_type; };STL中的做法十分簡單,其通過模板的特化來實現這個功能。對于泛化類型,它默認它的這些參數都是false的,而后再對所有的內置類型進行特化,將它們typedef為true,簡單高效的完成了這個功能。
C++標準庫類型萃取
C++標準庫中的類型萃取功能更加強大,但是實現也更加的復雜
SFINAE機制與enable_if
SFINAE(Substitution Failure is Not An Error)是C++ 的一種語言屬性,它的核心就是從一組重載函數中刪除模板實例化無效的函數——在編譯期編譯時,會將函數模板的形參替換為實參,如果替換失敗編譯器不會當作是個錯誤,直到找到那個最合適的特化版本,如果所有的模板版本都替換失敗那編譯器就會報錯。
這里用enable_if來舉個例子
template <bool _Test, class _Ty = void> struct enable_if {}; template <class _Ty> struct enable_if<true, _Ty> using type = _Ty; };template <bool _Test, class _Ty = void> using enable_if_t = typename enable_if<_Test, _Ty>::type;從源碼我們可以看到,只有當第一個模板參數為true的時候,由于偏特化,其能夠匹配到第二個模板,此時的type才是有效的。而對于其他情況來說,編譯器都會直接忽略,直到匹配所有模板都失敗后,此時編譯器才會進行報錯。
通過SFINAE技術可以完成很多有趣的事,比如根據參數類型做不同的定制化操作。
conditional
為了能利用模板來實現如or、and等邏輯判斷,type_traits還實現了條件模板conditional
template <bool _Test, class _Ty1, class _Ty2> struct conditional { // Choose _Ty1 if _Test is true, and _Ty2 otherwiseusing type = _Ty1; };template <class _Ty1, class _Ty2> struct conditional<false, _Ty1, _Ty2> {using type = _Ty2; };template <bool _Test, class _Ty1, class _Ty2> using conditional_t = typename conditional<_Test, _Ty1, _Ty2>::type;其泛化時默認type為第二個參數,而對于第一個參數為false的特化版本,則會使用第二個參數。
核心結構:integral_constant 與bool_constant
type_traits最核心的結構就是integral_constant與bool_constant,下面給出了它們的源代碼
integral_constant是類型萃取最底層的結構,其借助模板使我們能夠獲取到KEY與VALUE的值與類型。同時,integral_constant重載了類型轉換操作符與()操作符,使其能夠像一個函數一樣進行調用。
template <class _Ty, _Ty _Val> struct integral_constant {static constexpr _Ty value = _Val;using value_type = _Ty;using type = integral_constant; //類型轉換運算符重載,將對象轉換為value_type時隱式調用constexpr operator value_type() const noexcept {return value;}//()運算符重載,用于實現仿函數_NODISCARD constexpr value_type operator()() const noexcept {return value;} };接著我們來分析bool_constant,其實它就是利用模板來對integral_constant進行了一層封裝。它用來檢查模板類型是否為某種類型,通過bool_constant我們可以獲取編譯期檢查的bool值結果。
template <bool _Val> using bool_constant = integral_constant<bool, _Val>;using true_type = bool_constant<true>; using false_type = bool_constant<false>;is_same
接下來我們看看is_same這個類,下面給出源碼:
template <class _Ty1, class _Ty2> struct is_same : bool_constant<is_same_v<_Ty1, _Ty2>> {};如果只從調用關系看,我們很容易將其看成一個函數,但是實際上它其實繼承于bool_constant,是一個仿函數對象。
它的主要作用是判斷兩個對象的類型是否相同,從源碼我們可以看出,這里主要依賴了is_same_v這個模板變量。
//借助模板的匹配規則判斷是否屬于相同類型 template <class, class> _INLINE_VAR constexpr bool is_same_v = false;template <class _Ty> _INLINE_VAR constexpr bool is_same_v<_Ty, _Ty> = true;is_same_v借助模板的隱式匹配規則,來判斷兩個參數是否屬于同一個類型,只有兩個模板參數的類型都相同時,才為true。
類型轉換:remove/add_xx()
在類型萃取的時候,為了排除不必要的干擾,我們通常會對數據的原類型進行一些處理,比如去掉const、volatile、右值引用等屬性。
下面我們來看看比較常用的remove_const和remove_volatile的源碼:
//去除頂層const template <class _Ty> struct remove_const { using type = _Ty; };template <class _Ty> struct remove_const<const _Ty> {using type = _Ty; };template <class _Ty> using remove_const_t = typename remove_const<_Ty>::type;// 去除頂層volatile template <class _Ty> struct remove_volatile { using type = _Ty; };template <class _Ty> struct remove_volatile<volatile _Ty> {using type = _Ty; };template <class _Ty> using remove_volatile_t = typename remove_volatile<_Ty>::type;它們的實現思路相同,都是利用了模板的偏特化來完成類型的消除,當我們的參數匹配到特化版本時,就會通過using關鍵字將type指定為泛化版本,完成消除。
通常,這兩個仿函數會搭配使用,因此標準庫又封裝它們再次成remove_cv
// STRUCT TEMPLATE remove_cv template <class _Ty> struct remove_cv { // remove top-level const and volatile qualifiersusing type = _Ty;template <template <class> class _Fn>using _Apply = _Fn<_Ty>; // apply cv-qualifiers from the class template argument to _Fn<_Ty> };template <class _Ty> struct remove_cv<const _Ty> {using type = _Ty;template <template <class> class _Fn>using _Apply = const _Fn<_Ty>; };template <class _Ty> struct remove_cv<volatile _Ty> {using type = _Ty;template <template <class> class _Fn>using _Apply = volatile _Fn<_Ty>; };template <class _Ty> struct remove_cv<const volatile _Ty> {using type = _Ty;template <template <class> class _Fn>using _Apply = const volatile _Fn<_Ty>; };template <class _Ty> using remove_cv_t = typename remove_cv<_Ty>::type;實戰分析:is_void
上面講了一些類型萃取的關鍵函數,下面就來實際看一看它們到底是怎么運作的,首先選取最簡單的is_void來分析。
// STRUCT TEMPLATE is_void template <class _Ty> _INLINE_VAR constexpr bool is_void_v = is_same_v<remove_cv_t<_Ty>, void>;template <class _Ty> struct is_void : bool_constant<is_void_v<_Ty>> {};我們從內往外開始分析,它的執行流程如下
下面我們來實際使用一下
#include<iostream> #include<type_traits> using namespace std;int main() {cout << typeid(is_void<int>::type).name() << endl;cout << is_void<int>::value << endl;cout << typeid(is_void<void>::type).name() << endl;cout << is_void<void>::value << endl;return 0; }------------輸出結果------------ struct std::integral_constant<bool,0> 0 struct std::integral_constant<bool,1> 1至于其他一些類型的萃取函數,其實都與上面的大致相同,唯一不同的地方就是is_type_v這里的類型判斷的邏輯,例如:
template <class _Ty> struct is_union : bool_constant<__is_union(_Ty)> {}; // determine whether _Ty is a uniontemplate <class _Ty> _INLINE_VAR constexpr bool is_union_v = __is_union(_Ty);// STRUCT TEMPLATE is_class template <class _Ty> struct is_class : bool_constant<__is_class(_Ty)> {}; // determine whether _Ty is a classtemplate <class _Ty> _INLINE_VAR constexpr bool is_class_v = __is_class(_Ty);// STRUCT TEMPLATE is_fundamental template <class _Ty> _INLINE_VAR constexpr bool is_fundamental_v = is_arithmetic_v<_Ty> || is_void_v<_Ty> || is_null_pointer_v<_Ty>;template <class _Ty> struct is_fundamental : bool_constant<is_fundamental_v<_Ty>> {}; // determine whether _Ty is a fundamental type遺憾的是C++標準庫中并沒有給出這些復雜類型的判斷代碼,在這里也就不過多的進行講解了。
總結
以上是生活随笔為你收集整理的还搞不懂STL的type_traits?从源码来带你一起分析的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 战斗民族开源神器。ClickHouse为
- 下一篇: ClickHouse 数据存储原理:Me