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類的: