Item 47: Use traits classes for information about types.
C++中的 Traits 類可以在編譯期提供類型信息,它是用Traits模板及其特化來實現的。 通過方法的重載,可以在編譯期對類型進行”if…else”判斷。我們通過STL中的一個例子來介紹Traits的實現和使用。
本文以
iterator_traits
為例介紹了如何實現traits類,以及如何使用traits類(在Item 42中提到過iterator_traits
)。 其實C++標准庫中還提供了很多其他的traits,比如char_traits
,numeric_limits
等。
STL提供了很多的容器、迭代器和算法,其中的advance
便是一個通用的算法,可以讓一個迭代器移動n步:
template
void advance(IterT& iter, DistT d); // 如果d小於0,就逆向移動
最簡單的迭代器是輸入迭代器(input iterator)和輸出迭代器(output iterator), 它們只能向前移動,可以讀取/寫入它的當前位置,但只能讀寫一次。比如ostream_iterator
就是一個輸出迭代器。
比它們稍強的是前向迭代器(forward iterator),可以多次讀寫它的當前位置。 單向鏈表(slist
,STL並未提供)和TR1哈希容器的迭代器就屬於前向迭代器。
雙向迭代器(bidirectional iterator)支持前後移動,支持它的容器包括set
,multiset
,map
,multimap
。
隨機訪問迭代器(random access iterator)是最強的一類迭代器,可以支持+=
,-=
等移動操作,支持它的容器包括vector
,deque
,string
等。
對於上述五種迭代器,C++提供了五種Tag來標識迭代器的類型,它們之間是”is-a”的關系:
struct input_iterator_tag {};
struct output_iterator_tag {};
struct forward_iterator_tag: public input_iterator_tag {};
struct bidirectional_iterator_tag: public forward_iterator_tag {};
struct random_access_iterator_tag: public bidirectional_iterator_tag {};
現在回到advance
的問題,它的實現方式顯然取決於Iter
的類型:
template
void advance(IterT& iter, DistT d){
if (iter is a random access iterator) {
iter += d; // use iterator arithmetic
} // for random access iters
else {
if (d >= 0) { while (d--) ++iter; } // use iterative calls to
else { while (d++) --iter; } // ++ or -- for other
} // iterator categories
}
怎麼得到Iter
的類型呢?這正是traits的作用。
traits允許我們在編譯期得到類型的信息。traits並非一個關鍵字,而是一個編程慣例。
traits的另一個需求在於advance
對與基本數據類型也能正常工作,比如char*
。所以traits不能借助類來實現, 於是我們把traits放到模板中。比如:
template // template for information about
struct iterator_traits; // iterator types
iterator_traits
將會標識IterT
的迭代器類別。iterator_traits
的實現包括兩部分:
在用戶定義的類型中,typedef該類型支持迭代器的Tag,例如deque
支持隨機迭代器:
template < ... > // template params elided
class deque {
public:
class iterator {
public:
typedef random_access_iterator_tag iterator_category;
}:
};
然後在全局的iterator_traits
模板中typedef
那個用戶類型中的Tag,以提供全局和統一的類型識別。
template
struct iterator_traits {
typedef typename IterT::iterator_category iterator_category;
};
上述辦法對基本數據類型的指針是不起作用的,我們總不能在指針裡面typedef
一個Tag吧? 其實這時只需要偏特化iterator_traits
,因為內置類型指針都是可以隨機訪問的:
template // partial template specialization
struct iterator_traits{
typedef random_access_iterator_tag iterator_category;
};
你已經看到了實現一個traits類的整個過程:
deque
的iterator
類型;iterator_catetory
;iterator_traits
。
我們已經用iterator_traits
提供了迭代器的類型信息,是時候給出advance
的實現了。
template
void advance(IterT& iter, DistT d) {
if (typeid(typename std::iterator_traits::iterator_category) ==
typeid(std::random_access_iterator_tag))
...
}
上述實現其實並不完美,至少if
語句中的條件在編譯時就已經決定,它的判斷卻推遲到了運行時(顯然是低效的)。 在編譯時作此判斷,需要為不同的iterator
提供不同的方法,然後在advance
裡調用它們。
template
void advance(IterT& iter, DistT d) {
doAdvance( // call the version
iter, d, // of doAdvance
typename std::iterator_traits::iterator_category()
);
}
// 隨機訪問迭代器
template
void doAdvance(IterT& iter, DistT d, std::random_access_iterator_tag) {
iter += d;
}
// 雙向迭代器
template
void doAdvance(IterT& iter, DistT d, std::bidirectional_iterator_tag) {
if (d >= 0) { while (d--) ++iter; }
else { while (d++) --iter; }
}
// 輸入迭代器
template
void doAdvance(IterT& iter, DistT d, std::input_iterator_tag) {
if (d < 0 ) {
throw std::out_of_range("Negative distance"); // see below
}
while (d--) ++iter;
}
總結一下上面代碼是如何使用traits類的: