程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> 關於C++ >> C++ traits技術淺談

C++ traits技術淺談

編輯:關於C++

C++ traits技術淺談。本站提示廣大學習愛好者:(C++ traits技術淺談)文章只能為提供參考,不一定能成為您想要的結果。以下是C++ traits技術淺談正文


前言

 

  traits,又被叫做特性萃取技術,說得簡單點就是提取“被傳進的對象”對應的返回類型,讓同一個接口實現對應的功能。因為STL的算法和容器是分離的,兩者通過迭代器鏈接。算法的實現並不知道自己被傳進來什麼。萃取器相當於在接口和實現之間加一層封裝,來隱藏一些細節並協助調用合適的方法,這需要一些技巧(例如,偏特化)。最後附帶一個小小的例子,應該能更好地理解 特性萃取。

  下面大部分來源於《STL源碼剖析》,看原書能了解更多細節。

 

Traits編程技法

  讓我們一點點拋出問題,然後一點點深入。

  1. 首先,在算法中運用迭代器時,很可能會用到其相應型別(迭代器所指之物的型別)。假設算法中有必要聲明一個變量,以“迭代器所指對象的型別”為型別,該怎麼辦呢?

  解決方法是:利用function template的參數推導機制。

 1 template <class I, class T>
 2 void func_impl(I iter, T t) {
 3         T tmp; // 這裡就是迭代器所指物的類型新建的對象
 4         // ... 功能實現
 5 }
 6 
 7 template <class I>
 8 inline
 9 void func(I iter) {
10         func_impl(iter, *iter); // 傳入iter和iter所指的值,class自動推導
11 }
12 
13 int main() {
14     int i;
15     func(&i);
16 }

  這裡已經可以看出封裝的意思了,沒有一層impl的封裝的話,每次你都要顯式地說明迭代器指向對象型別,才能新建tmp變量。加一層封裝顯得清爽很多。

  迭代器相應型別不只是“迭代器所指對象的型別”一種而已。根據經驗,最常用的相應型別有五種,然而並非任何情況下任何一種都可以利用上述的template參數推導機制來取得。

  函數的“template參數推導機制”推導的只是參數,無法推導函數的返回值類型。萬一需要推導函數的傳回值,就無能為力了。

 

  2.  聲明內嵌型別似乎是個好主意,這樣我們就可以直接獲取。

 1 template <class T>
 2 struct MyIter {
 3     typedef T value_type; // 內嵌型別聲明
 4     // ...
 5 };
 6 
 7 template <class I>
 8 typename I::value_type
 9 func(I ite) {
10     return *ite;
11 }
12 
13 // ...
14 MyIter<int> ite(new int(8));
15 cout << func(ite);

  看起來不錯,但是並不是所有迭代器都是class type,原生指針就不行!如果不是class type,就無法為它定義內嵌型別。

  這時候就需要 偏特化 出現。

 

  3. 偏特化就是在特化的基礎上再加一點限制,但它還是特化的template。

 1 template <class I>
 2 struct iterator_traits {
 3     typedef typename I::value_type value_type;
 4 };
 5 
 6 template <class I>
 7 struct iterator_traits<T*> {
 8     typedef T value_type;
 9 };
10 
11 template <class I>12 typename iterator_traits<I>::value_type
13 func(I ite) {
14     return *ite;
15 }

  func在調用 I 的時候,首先把 I 傳到萃取器中,然後萃取器就匹配最適合的 value_type。(萃取器會先匹配最特別的版本)這樣當你傳進一個原生指針的時候,首先匹配的是帶<T*>的偏特化版本,這樣 value_type 就是 T,而不是沒有事先聲明的 I::value_type。這樣返回值就可以使用 typename iterator_traits<I>::value_type 來知道返回類型。

  下面附上《STL源碼剖析》的圖片:

 

讓traits干更多東西

 

  迭代器有常見有五種類型: value_type, difference_type, reference_type, pointer_type都比較容易在 traits 和 相應偏特化中提取。但是,iterator_category一般也有5個,這個相應型別會引發較大規模的寫代碼工程。

  例如,我們實現了 func_II, func_BI, func_RAI 分別代表迭代器類型是Input Iterator,Bidirectional Iterator和Random Access Iterator的對應實現。

  現在,當客端調用func()的時候,我們可能需要做一個判斷:

1 template<class Iterator>
2 void func(Iterator& i) {
3     if (is_random_access_iterator(i))
4         func_RAI(i);
5     if (is_bidirectional_iterator(i))
6         func_BI(i);
7     else
8         func_II(i);
9 }

  但這樣在執行時期才決定使用哪一個版本,會影響程序效率。最好能夠在編譯期就選擇正確的版本。

  重載這個函數機制可以達成這個目標。

1 struct input_iterator_tag {};
2 struct output_iterator_tag {};
3 struct forward_iterator_tag : public input_iterator_tag {};
4 // ...
5 // 繼承的好處就是,當函數需要用 input_iterator_tag 的時候
6 // 假設你傳進一個forward_iterator_tag,它會沿繼承向上找,知道符合條件

 

   聲明了一些列 tag 之後,我們就可以重載 func函數: func(tag)。

  到這裡,各個型別的具體重載實現已經寫好,但是需要一個統一的接口,這時候 traits 就可以出場了。

1 template<class Iterator>
2 inline void func(Iterator& i)
3 {
4     typedef typename Iterator_traits<Iterator>::iterator_category category;
5     __func(i, category()); // 各型別的重載
6 }

 

 

簡單實例代碼

 

  所以說,traits一方面,在面對不同的輸入類時,能找到合適的返回型別;另一方面,當型別對應有不同的實現函數的時候,能起到一個提取型別然後分流的作用。

  先假設我們有一個 func 函數,可以接受 自定義的類 或者 原始的指針 作為參數,並自動輸出使用了什麼tag。

  首先根據 traits(由本身或偏特化版本實現) ,它會提取 u 的返回型別,然後調用對應的構造函數 return_type(), 來當作各個重載版本 __func 的重載標志區分不同的實際函數。

  

  • 首先我們看看接口代碼的編寫
    1 template <class unknown_class>
    2 inline typename unknown_class_traits<unknown_class>::return_type // 萃取器取得對應型別
    3 func(unknown_class u) {
    4     typedef typename unknown_class_traits<unknown_class>::return_type return_type;
    5     return __func(u, return_type()); // 需要調用構造函數當tag
    6 }
  • 先 return_type 的構造函數
    1 template <class unknown_class>
    2 inline typename unknown_class_traits<unknown_class>::return_type
    3 return_type(unknown_class) {
    4     typedef typename unknown_class_traits<unknown_class>::return_type RT;
    5     return RT();
    6 }

    然後是實現設定的 tag ,用來模仿前面說的 II,RAI等

    1 struct A {};
    2 struct B : A{};
  • 然後是 traits 隆重登場,有兩個偏特化版本。
     1 /*特性萃取器*/
     2 template <class unknown_class>
     3 struct unknown_class_traits {
     4     typedef typename unknown_class::return_type return_type;
     5 };
     6 
     7 /*特性萃取器 —— 針對原生指針*/
     8 template <class T>
     9 struct unknown_class_traits<T*> {
    10     typedef T return_type;
    11 };
    12 
    13 /*特性萃取其 —— 針對指向常數*/
    14 template <class T>
    15 struct unknown_class_traits<const T*> {
    16     typedef const T return_type;
    17 };
  • 突然忘記了交代 unknown_class 的結構,自定義的類,必須要 typedef。
    1 template <class AorB>
    2 struct unknown_class {
    3     typedef AorB return_type;
    4 };
  • 最後是func各個重載版本。
     1 template <class unknown_class>
     2 inline typename unknown_class_traits<unknown_class>::return_type
     3 __func(unknown_class, A) {
     4     cout << "use A flag" << endl;
     5     return A();
     6 }
     7 
     8 template <class unknown_class>
     9 inline typename unknown_class_traits<unknown_class>::return_type
    10 __func(unknown_class, B) {
    11     cout << "use B flag" << endl;
    12     return B();
    13 }
    14 
    15 template <class unknown_class, class T>
    16 T
    17 __func(unknown_class, T) {
    18     cout << "use origin ptr" << endl;
    19     return T();
    20 }

     

  • 有了這些我們就可以測試了
     1 int main() {
     2     unknown_class<B> b;
     3     unknown_class<A> a;
     4     //unknown_class<int> i;
     5     int value = 1;
     6     int *p = &value;
     7 
     8     A v1 = func(a);
     9     B v2 = func(b);
    10     int v3 = func(p);
    11 
    12     char ch = getchar();
    13 } 

  可以看到,對於用自定義類傳入同一個接口,它會自動使用對應的函數,而且返回值也合適。對原始指針也適用,完美!

 


 

 下面是完整代碼:

 1 #include <iostream>
 2 using namespace std;
 3 
 4 /*先定義一些tag*/
 5 struct A {};
 6 struct B : A{}; // 繼承的好處就是,當函數需要參數為A,
 7                 // 而你傳入的參數為B的時候,可以往上一直找到適合的對象
 8 
 9 /*假設有一個未知類*/
10 template <class AorB>
11 struct unknown_class {
12     typedef AorB return_type;
13 };
14 
15 /*特性萃取器*/
16 template <class unknown_class>
17 struct unknown_class_traits {
18     typedef typename unknown_class::return_type return_type;
19 };
20 
21 /*特性萃取器 —— 針對原生指針*/
22 template <class T>
23 struct unknown_class_traits<T*> {
24     typedef T return_type;
25 };
26 
27 /*特性萃取其 —— 針對指向常數*/
28 template <class T>
29 struct unknown_class_traits<const T*> {
30     typedef const T return_type;
31 };
32 
33 
34 /*決定使用哪一個類型*/
35 template <class unknown_class>
36 inline typename unknown_class_traits<unknown_class>::return_type
37 return_type(unknown_class) {
38     typedef typename unknown_class_traits<unknown_class>::return_type RT;
39     return RT();
40 }
41 
42 template <class unknown_class>
43 inline typename unknown_class_traits<unknown_class>::return_type
44 __func(unknown_class, A) {
45     cout << "use A flag" << endl;
46     return A();
47 }
48 
49 template <class unknown_class>
50 inline typename unknown_class_traits<unknown_class>::return_type
51 __func(unknown_class, B) {
52     cout << "use B flag" << endl;
53     return B();
54 }
55 
56 template <class unknown_class, class T>
57 T
58 __func(unknown_class, T) {
59     cout << "use origin ptr" << endl;
60     return T();
61 }
62 
63 template <class unknown_class>
64 inline typename unknown_class_traits<unknown_class>::return_type
65 func(unknown_class u) {
66     typedef typename unknown_class_traits<unknown_class>::return_type return_type;
67     return __func(u, return_type());
68 }
69 
70 int main() {
71     unknown_class<B> b;
72     unknown_class<A> a;
73     //unknown_class<int> i;
74     int value = 1;
75     int *p = &value;
76 
77     A v1 = func(a);
78     B v2 = func(b);
79     int v3 = func(p);
80 
81     char ch = getchar();
82 }

 

 結束語

  特性提取花了自己好多時間,不過當程序跑出來的瞬間還是挺開心的。

  首先要感謝侯捷老師,老師的書講得這麼清楚,我還是笨笨的看得一知半解。

  看完這個可以看圖像的傅裡葉變換啦,啊哈哈~

  

 

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved