C 型別萃取技術

2021-09-01 14:31:02 字數 4132 閱讀 4689

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...