traits技術可以用來獲得乙個 型別 的相關資訊的。
template
class myiterator
當我們使用myiterator時,怎樣才能獲知它所指向的元素的型別呢?我們可以為這個類加入乙個內嵌型別,像這樣:
template
class myiterator
typedef t value_type;
這樣當我們使用myiterator型別時,可以通過 myiterator::value_type來獲得相應的myiterator所指向的型別。
現在我們來設計乙個演算法,使用這個資訊。
template
typename myiterator::value_type foo(myiteratori)
這裡我們定義了乙個函式foo,它的返回為為 引數i 所指向的型別,也就是t,那麼我們為什麼還要興師動眾的使用那個value_type呢? 那是因為,當我們希望修改foo函式,使它能夠適應所有型別的迭代器時,我們可以這樣寫:
template //這裡的i可以是任意型別的迭代器
typename i::value_type foo(i i)
現在,任意定義了 value_type內嵌型別的迭代器都可以做為foo的引數了,並且foo的返回值的型別將與相應迭代器所指的元素的型別一致。至此一切問題似乎都已解決,我們並沒有使用任何特殊的技術。然而當考慮到以下情況時,新的問題便顯現出來了:
原生指標也完全可以做為迭代器來使用,然而我們顯然沒有辦法為原生指標新增乙個value_type的內嵌型別,如此一來我們的foo()函式就不能適用原生指標了,這不能不說是一大缺憾。那麼有什麼辦法可以解決這個問題呢? 此時便是我們的主角:型別資訊萃取技術traits 登場的時候了
我們可以不直接使用myiterator的value_type,而是通過另乙個類來把這個資訊提取出來:
template
class traits
typedef typename t::value_type value_type;
這樣,我們可以通過 traits::value_type 來獲得myiterator的value_type,於是我們把foo函式改寫成:
template //這裡的i可以是任意型別的迭代器
typename traits::value_type foo(i i)
然而,即使這樣,那個原生指標的問題仍然沒有解決,因為trait類一樣沒辦法獲得原生指標的相關資訊。於是我們祭出c++的又一件利器--偏特化(partial specialization):
template
class traits//注意 這裡針對原生指標進行了偏特化
typedef typename t value_type;
通過上面這個 traits的偏特化版本,我們陳述了這樣乙個事實:乙個 t* 型別的指標所指向的元素的型別為 t。
如此一來,我們的 foo函式就完全可以適用於原生指標了。比如:
int * p;
int i = foo(p);
traits會自動推導出 p 所指元素的型別為 int,從而foo正確返回。
自從c++中引入了template後,以泛型技術為中心的設計得到了長足的進步。stl就是這個階段傑出的產物。stl的目標就是要把資料和演算法分開,分別對其進行設計,之後通過一種名為iterator的東西,把這二者再粘接到一起。設計模式中,關於iterator的描述為:一種能夠順序訪問容器中每個元素的方法,使用該方法不能暴露容器內部的表達方式。可以說,型別萃取技術就是為了要解決和iterator有關的問題的,下面,我們就來看看整個故事。
應該說,迭代器就是一種智慧型指標,因此,它也就擁有了一般指標的所有特點——能夠對其進行*和->操作。但是在遍歷容器的時候,不可避免的要對遍歷的容器內部有所了解,所以,設計乙個迭代器也就自然而然的變成了資料結構開發者的乙個義務,而這些iterators的表現都是一樣的,這種內外的差異,對使用者來說,是完全透明的,
第一部分 為什麼要有萃取技術
既然是一種智慧型指標,iterator也要對乙個原生指標進行封裝,而問題就源於此,當我們需要這個原生指標所指物件的型別的時候(例如宣告變數),怎麼辦呢?
case1 對於函式的區域性變數
這種情況我們可以採用模版的引數推導,例如:
template void func(t iter)
如果t是某個指向特定物件的指標,那麼在func中需要指標所指向物件型別的變數的時候,怎麼辦呢?這個還比較容易,模板的引數推導機制可以完成任務,如下:
template
void func_impl(t t, u u)
t& operator*() const
而後只要需要其指向的物件的型別,只要直接引用就好了,例如:
template
typename i::value_type func(i iter)
很漂亮的解決方案,看上去一切都很完美。但是,實際上還是有問題,因為func如果是乙個泛型演算法,那麼它也絕對要接受乙個原生指標作為迭代器,但是顯然,你無法讓下面的**編譯通過:
int *p = new int(52);
cout《我們的func無法支援原生指標,這顯然是不能接受的。此時,template partial specialization就派上了用場。
solution :template partial specialization是救世主
既然剛才的設計方案仍不完美,那我們就再加乙個間接層,把智慧型指標和原生指標統統的封裝起來。在討論之前,先要澄清一下template partial specialization的含義。所謂的partial specialization和模板的預設引數是完全不同的兩件事情,前者指的是當引數為某一類特定型別的時候,採用特殊的設計,也就是說是「針對template引數更進一步的條件限制所設計出來的乙個特化版本」;而預設引數則是當不提供某些引數的時候,使用的乙個預設。
參考:partial specialization的語法
template class c{} // 為所有型別為t*的引數而準備的特殊版本
好了,下面我們就找乙個專職的負責人,用來封裝迭代器封裝的物件型別。首先,把我們剛才的myiter重新包裝一下:
template
struct iterator_traits {
typedef i::value_type value_type;
現在,我們的func又有了新的面貌:
template
typename iterator_traits::value_type func(i ite) {
return *ite;
儘管這次我們的函式返回值的長度有些嚇人,但是,我們的確為原生指標找到了好的解決方案。只要為原生指標提供乙個偏特化的iterator_traits就ok了。如下:
template
struct iterator_traits{
typedef t value_type;
下面,我們終於可以讓func同時支援智慧型指標和原生指標了:
template
struct iterator_traits {
typedef i::value_type value_type;
template
struct iterator_traits{
typedef t value_type;
template
typename iterator_traits::value_type func(i ite) {
return *ite;
int main() {
myiteriter = new int(520);
int *p = new int(520);
// this time the following two statements will success
cout但是,我們離最後的成功還有最後一步,如果,我們需要宣告乙個value_type型別的左值,但是卻給iterator_traits傳遞了乙個const int*,顯然結果有問題,於是,為const t*也另起爐灶,準備乙份小炒:
template
struct iterator_traits{
typedef t value_type;
ok ,現在萬事大吉,無論是正宗迭代器,原生指標,const原生指標,我們都可以利用iterator_traits萃取出其封裝的物件的型別,萃取技術由此而來。
C 型別萃取
在c 中我們可以通過typeid來獲取乙個型別的名稱 內建型別和自定義型別都可以 但是我們不能用這種方式獲取來的名稱做變數的宣告。那麼在c 中怎樣識別物件的型別呢?我們可以通過型別萃取的方式來區分內建型別和自定義型別。例如 我們在seqlist中要用到型別萃取,因為內建型別我們可以通過memcopy...
C 型別萃取
當我們遇到這樣的場景時,我們會用到型別萃取 template void copy t dst,t str,size t n 模板函式copy void test string s2 10 int l1 10 int l2 10 copy s1,s2,10 copy l1,l2,10 for size...
C 型別萃取
型別萃取依靠的就是模版的特化,模版的特化又分為全特化和偏特化,根據不同的情況做相應的呼叫。函式模版特化 函式模版只有全特化,而沒有偏特化。沒有偏特化的原因是已經有了函式過載。通用模版並不總是正確的,在某些情況下有可能是錯誤的。例如 include using namespace std templa...