深入淺析C++ traits技術(shù)
前言
traits,又被叫做特性萃取技術(shù),說得簡單點就是提取“被傳進的對象”對應(yīng)的返回類型,讓同一個接口實現(xiàn)對應(yīng)的功能。因為STL的算法和容器是分離的,兩者通過迭代器鏈接。算法的實現(xiàn)并不知道自己被傳進來什么。萃取器相當(dāng)于在接口和實現(xiàn)之間加一層封裝,來隱藏一些細節(jié)并協(xié)助調(diào)用合適的方法,這需要一些技巧(例如,偏特化)。最后附帶一個小小的例子,應(yīng)該能更好地理解 特性萃取。
下面大部分來源于《STL源碼剖析》,看原書能了解更多細節(jié)。
Traits編程技法
讓我們一點點拋出問題,然后一點點深入。
1. 首先,在算法中運用迭代器時,很可能會用到其相應(yīng)型別(迭代器所指之物的型別)。假設(shè)算法中有必要聲明一個變量,以“迭代器所指對象的型別”為型別,該怎么辦呢?
解決方法是:利用function template的參數(shù)推導(dǎo)機制。
template <class I, class T>
void func_impl(I iter, T t) {
T tmp; // 這里就是迭代器所指物的類型新建的對象
// ... 功能實現(xiàn)
}
template <class I>
inline
void func(I iter) {
func_impl(iter, *iter); // 傳入iter和iter所指的值,class自動推導(dǎo)
}
int main() {
int i;
func(&i);
}
這里已經(jīng)可以看出封裝的意思了,沒有一層impl的封裝的話,每次你都要顯式地說明迭代器指向?qū)ο笮蛣e,才能新建tmp變量。加一層封裝顯得清爽很多。
迭代器相應(yīng)型別不只是“迭代器所指對象的型別”一種而已。根據(jù)經(jīng)驗,最常用的相應(yīng)型別有五種,然而并非任何情況下任何一種都可以利用上述的template參數(shù)推導(dǎo)機制來取得。
函數(shù)的“template參數(shù)推導(dǎo)機制”推導(dǎo)的只是參數(shù),無法推導(dǎo)函數(shù)的返回值類型。萬一需要推導(dǎo)函數(shù)的傳回值,就無能為力了。
2. 聲明內(nèi)嵌型別似乎是個好主意,這樣我們就可以直接獲取。
template <class T>
struct MyIter {
typedef T value_type; // 內(nèi)嵌型別聲明
// ...
};
template <class I>
typename I::value_type
func(I ite) {
return *ite;
}
// ...
MyIter<int> ite(new int(8));
cout << func(ite);
看起來不錯,但是并不是所有迭代器都是class type,原生指針就不行!如果不是class type,就無法為它定義內(nèi)嵌型別。
這時候就需要 偏特化 出現(xiàn)。
3. 偏特化就是在特化的基礎(chǔ)上再加一點限制,但它還是特化的template。
template <class I>
struct iterator_traits {
typedef typename I::value_type value_type;
};
template <class I>
struct iterator_traits<T*> {
typedef T value_type;
};
template <class I>12 typename iterator_traits<I>::value_type
func(I ite) {
return *ite;
}
func在調(diào)用 I 的時候,首先把 I 傳到萃取器中,然后萃取器就匹配最適合的 value_type。(萃取器會先匹配最特別的版本)這樣當(dāng)你傳進一個原生指針的時候,首先匹配的是帶<T*>的偏特化版本,這樣 value_type 就是 T,而不是沒有事先聲明的 I::value_type。這樣返回值就可以使用 typename iterator_traits<I>::value_type 來知道返回類型。
下面附上《STL源碼剖析》的圖片:

讓traits干更多東西
迭代器有常見有五種類型: value_type, difference_type, reference_type, pointer_type都比較容易在 traits 和 相應(yīng)偏特化中提取。但是,iterator_category一般也有5個,這個相應(yīng)型別會引發(fā)較大規(guī)模的寫代碼工程。
例如,我們實現(xiàn)了 func_II, func_BI, func_RAI 分別代表迭代器類型是Input Iterator,Bidirectional Iterator和Random Access Iterator的對應(yīng)實現(xiàn)。
現(xiàn)在,當(dāng)客端調(diào)用func()的時候,我們可能需要做一個判斷:
template<class Iterator>
void func(Iterator& i) {
if (is_random_access_iterator(i))
func_RAI(i);
if (is_bidirectional_iterator(i))
func_BI(i);
else
func_II(i);
}
但這樣在執(zhí)行時期才決定使用哪一個版本,會影響程序效率。最好能夠在編譯期就選擇正確的版本。
重載這個函數(shù)機制可以達成這個目標(biāo)。
struct input_iterator_tag {};
struct output_iterator_tag {};
struct forward_iterator_tag : public input_iterator_tag {};
// ...
// 繼承的好處就是,當(dāng)函數(shù)需要用 input_iterator_tag 的時候
// 假設(shè)你傳進一個forward_iterator_tag,它會沿繼承向上找,知道符合條件
聲明了一些列 tag 之后,我們就可以重載 func函數(shù): func(tag)。
到這里,各個型別的具體重載實現(xiàn)已經(jīng)寫好,但是需要一個統(tǒng)一的接口,這時候 traits 就可以出場了。
template<class Iterator>
inline void func(Iterator& i)
{
typedef typename Iterator_traits<Iterator>::iterator_category category;
__func(i, category()); // 各型別的重載
}
簡單實例代碼
所以說,traits一方面,在面對不同的輸入類時,能找到合適的返回型別;另一方面,當(dāng)型別對應(yīng)有不同的實現(xiàn)函數(shù)的時候,能起到一個提取型別然后分流的作用。
先假設(shè)我們有一個 func 函數(shù),可以接受 自定義的類 或者 原始的指針 作為參數(shù),并自動輸出使用了什么tag。
首先根據(jù) traits(由本身或偏特化版本實現(xiàn)) ,它會提取 u 的返回型別,然后調(diào)用對應(yīng)的構(gòu)造函數(shù) return_type(), 來當(dāng)作各個重載版本 __func 的重載標(biāo)志區(qū)分不同的實際函數(shù)。

首先我們看看接口代碼的編寫
template <class unknown_class>
inline typename unknown_class_traits<unknown_class>::return_type // 萃取器取得對應(yīng)型別
func(unknown_class u) {
typedef typename unknown_class_traits<unknown_class>::return_type return_type;
return __func(u, return_type()); // 需要調(diào)用構(gòu)造函數(shù)當(dāng)tag
}
然后是實現(xiàn)設(shè)定的 tag ,用來模仿前面說的 II,RAI等
template <class unknown_class>
inline typename unknown_class_traits<unknown_class>::return_type
return_type(unknown_class) {
typedef typename unknown_class_traits<unknown_class>::return_type RT;
return RT();
}
有了這些我們就可以測試了
struct A {};
struct B : A{};
然后是 traits 隆重登場,有兩個偏特化版本。
/*特性萃取器*/
template <class unknown_class>
struct unknown_class_traits {
typedef typename unknown_class::return_type return_type;
};
/*特性萃取器 —— 針對原生指針*/
template <class T>
struct unknown_class_traits<T*> {
typedef T return_type;
};
/*特性萃取其 —— 針對指向常數(shù)*/
template <class T>
struct unknown_class_traits<const T*> {
typedef const T return_type;
};
突然忘記了交代 unknown_class 的結(jié)構(gòu),自定義的類,必須要 typedef。
template <class AorB>
struct unknown_class {
typedef AorB return_type;
};
最后是func各個重載版本。
template <class unknown_class>
inline typename unknown_class_traits<unknown_class>::return_type
__func(unknown_class, A) {
cout << "use A flag" << endl;
return A();
}
template <class unknown_class>
inline typename unknown_class_traits<unknown_class>::return_type
__func(unknown_class, B) {
cout << "use B flag" << endl;
return B();
}
template <class unknown_class, class T>
T
__func(unknown_class, T) {
cout << "use origin ptr" << endl;
return T();
}
有了這些我們就可以測試了
int main() {
unknown_class<B> b;
unknown_class<A> a;
//unknown_class<int> i;
int value = 1;
int *p = &value;
A v1 = func(a);
B v2 = func(b);
int v3 = func(p);
char ch = getchar();
}
可以看到,對于用自定義類傳入同一個接口,它會自動使用對應(yīng)的函數(shù),而且返回值也合適。對原始指針也適用,完美!

附
下面是完整代碼:
#include <iostream>
using namespace std;
/*先定義一些tag*/
struct A {};
struct B : A{}; // 繼承的好處就是,當(dāng)函數(shù)需要參數(shù)為A,
// 而你傳入的參數(shù)為B的時候,可以往上一直找到適合的對象
/*假設(shè)有一個未知類*/
template <class AorB>
struct unknown_class {
typedef AorB return_type;
};
/*特性萃取器*/
template <class unknown_class>
struct unknown_class_traits {
typedef typename unknown_class::return_type return_type;
};
/*特性萃取器 —— 針對原生指針*/
template <class T>
struct unknown_class_traits<T*> {
typedef T return_type;
};
/*特性萃取其 —— 針對指向常數(shù)*/
template <class T>
struct unknown_class_traits<const T*> {
typedef const T return_type;
};
/*決定使用哪一個類型*/
template <class unknown_class>
inline typename unknown_class_traits<unknown_class>::return_type
return_type(unknown_class) {
typedef typename unknown_class_traits<unknown_class>::return_type RT;
return RT();
}
template <class unknown_class>
inline typename unknown_class_traits<unknown_class>::return_type
__func(unknown_class, A) {
cout << "use A flag" << endl;
return A();
}
template <class unknown_class>
inline typename unknown_class_traits<unknown_class>::return_type
__func(unknown_class, B) {
cout << "use B flag" << endl;
return B();
}
template <class unknown_class, class T>
T
__func(unknown_class, T) {
cout << "use origin ptr" << endl;
return T();
}
template <class unknown_class>
inline typename unknown_class_traits<unknown_class>::return_type
func(unknown_class u) {
typedef typename unknown_class_traits<unknown_class>::return_type return_type;
return __func(u, return_type());
}
int main() {
unknown_class<B> b;
unknown_class<A> a;
//unknown_class<int> i;
int value = 1;
int *p = &value;
A v1 = func(a);
B v2 = func(b);
int v3 = func(p);
char ch = getchar();
}
結(jié)束語
特性提取花了自己好多時間,不過當(dāng)程序跑出來的瞬間還是挺開心的。
首先要感謝侯捷老師,老師的書講得這么清楚,我還是笨笨的看得一知半解。
看完這個可以看圖像的傅里葉變換啦,啊哈哈~
以上就是C++ traits技術(shù)淺談的詳細內(nèi)容,更多關(guān)于C++ traits技術(shù)的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
詳細分析C++ 數(shù)據(jù)封裝和數(shù)據(jù)抽象
這篇文章主要介紹了C++ 數(shù)據(jù)封裝和數(shù)據(jù)抽象的的相關(guān)資料,文中代碼非常詳細,幫助大家更好的理解和學(xué)習(xí),感興趣的朋友可以了解下2020-06-06
C語言開發(fā)實現(xiàn)通訊錄管理系統(tǒng)
這篇文章主要為大家詳細介紹了C語言開發(fā)實現(xiàn)通訊錄管理系統(tǒng),文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-08-08

