剖析C++模板(上)

無類型的模板參數

       這裏有一個用來產生隨機數的類,它可以接受一個的數字,然後通過重載()符號,來產生一個符合要求的隨機數。具體代碼如下:
 

//: C03:Urand.h

// Unique random number generator

#ifndef URAND_H

#define URAND_H

#include <cstdlib>

#include <ctime>

template<int upperBound>

class Urand

{

int used[upperBound];

bool recycle;

public:

Urand(bool recycle = false);

int operator()(); // The "generator" function

};

 

template<int upperBound>

Urand<upperBound>::Urand(bool recyc)

: recycle(recyc)

{

memset(used, 0, upperBound * sizeof(int));

srand(time(0)); // Seed random number generator

}

template<int upperBound>

int Urand<upperBound>::operator()()

{

if(!memchr(used, 0, upperBound))

{

if(recycle)

memset(used,0,sizeof(used) * sizeof(int));

else

return -1; // No more spaces left

}

int newval;

while(used[newval = rand() % upperBound])

; // Until unique value is found

used[newval]++; // Set flag

return newval;

}

#endif // URAND_H ///:~
 

類Urand工作原理是這樣的:它保留了一個對所有可以取到的數字在隨即空間裏的MAP(映射)(隨機數的上限通過參數傳給模板),並且給使用過的數字打上標記。可控的構造函數允許你在所有使用完所有資源後,重新回收再用。請注意:這個類是從優化速度的角度出發來實現功能的,所以它爲所有實體都分配了映射空間。

 

缺省模板參數

關鍵字typename的使用

看下面的代碼:
 

//: C03:TypenamedID.cpp

// Using 'typename' to say it's a type,

// and not something other than a type

template<class T> class X

{

// Without typename, you should get an error:

typename T::id i;

public:

void f() { i.g(); }

};

 

class Y

{

public:

class id

{

public:

void g() {}

};

};

 

int main()

{

Y y;

X<Y> xy;

xy.f();

} ///:~

 

從模板中的定義可以看出:我們假設了類T中含有某個嵌套在類內部的也可以用來聲明變量的類型(type)——id。id可以是這個類T中的一個對象,通常情況下,你可以直接對id進行操作,但你不可以用id再creat一個其他的對象。然而在這裏,在typename的幫助下,我們卻做到了這一點。程序中Id被當作一個確確實實存在的類型來處理了,但如果我們丟掉了typename關鍵字,編譯器就無法知道id 究竟是個什麼東西了。

 

(在沒有typename的時候)編譯器會選擇並區分我們在模板中定義的東西究竟是什麼類型的,於是乎,它會把id當成其他的東西而不是一個類型(type),換句話說:它總是更樂意把標示看成一個對象(甚至是可變的私有類型),一個枚舉或者什麼類似的聲明。當然,它不會的——也不能——就把它看成是一個(類型)type,它沒這麼聰明,當我們把id作爲一個type使用的時候,編譯器會無法理解的。

 

Typename關鍵字告訴了編譯器把一個特殊的名字解釋成一個類型,在下列情況下必須對一個name使用typename關鍵字:

1.  一個唯一的name(可以作爲類型理解),它嵌套在另一個類型中的。

2.  依賴於一個模板參數,就是說:模板參數在某種程度上包含這個name。當模板參數使編譯器在指認一個類型時產生了誤解。

 

保險期間,你應該在所有編譯器可能錯把一個type當成一個變量的地方使用typename。就像上面那個例子中的T::id,因爲我們使用了typename,所以編譯器就知道了它是一個類型,可以用來聲明並創建實例。

 

給你一個簡明的使用指南:如果你的類型在模板參數中是有限制的,那你就必須使用typename.

 

用typename自定義一個類型

要知道typename關鍵字不會自動的typedef,

typename Seq::iterator It;

只是聲明瞭一個Seq::iterator類型的變量,如果你想定義一個新類型的話,你必須這樣:
 

typedef typename Seq::iterator It;

 

使用typename來代替class

詳細介紹了typename的使用方法之後,我們現在就可以選擇typename來取代class聲明,這樣可以增加程序的清晰度。
 

//: C03:UsingTypename.cpp

// Using 'typename' in the template argument list

template<typename T> class X { };

int main()

{

X<int> x;

} ///:~

 

你當然也會看到許多類似的代碼沒有使用typename關鍵字,因爲模板概念誕生之後很久了,纔有了typename關鍵字的加入。

函數模板

一個模板類描述的是一個無限的類的集合,你看到的是這些類中最普遍的地方。當然,C++也支持無限集合函數的概念,有是這是很有用的, 這些東西實質上是一樣的,除非你就是想聲明一個函數而不是一個類。

 

你可能已經知道了,我們要創建模板函數的原因,就是因爲我們發現很多函數看上去是完全一樣的,除了他們所處理的類型不同以外。並且,一個函數模板在許多地方都是非常有用的,就像我們在第一個例子中闡述的和我們將看到的第二個例子,它使用了容器(containers)和陳述(iterators)。

 

字符串轉換系統

//: C03:stringConv.h

// Chuck Allison's string converter

#ifndef STRINGCONV_H

#define STRINGCONV_H

#include <string>

#include <sstream>

template<typename T>

T fromString(const std::string& s)

{

    std::istringstream is(s);

    T t;

    is >> t;

    return t;

}

 

template<typename T>

std::string toString(const T& t)

{

    std::ostringstream s;

    s << t;

    return s.str();

}

#endif // STRINGCONV_H ///:~
 

這裏是測試程序,它包括了標準庫complex的使用:
 

//: C03:stringConvTest.cpp

#include "stringConv.h"

#include <iostream>

#include <complex>

using namespace std;

int main()

{

int i = 1234;

cout << "i == \"" << toString(i) << "\"\n";

float x = 567.89;

cout << "x == \"" << toString(x) << "\"\n";

complex<float> c(1.0, 2.0);

cout << "c == \"" << toString(c) << "\"\n";

cout << endl;

i = fromString<int>(string("1234"));

cout << "i == " << i << endl;

x = fromString<float>(string("567.89"));

cout << "x == " << x << endl;

c = fromString< complex<float> >(string("(1.0,2.0)"));

cout << "c == " << c << endl;

} ///:~

 

輸出結果是:

i == "1234"

x == "567.89"

c == "(1,2)"

i == 1234

x == 567.89

c == (1,2)

內存分配系統

在更安全使用malloc()、calloc()和realloc()等內存分配函數的議題中,我們有許多事可以做,接下來的函數模板處理了一個函數getmem(),這個函數即可以分配新的內存空間,或者調整以分配內存空間的大小,它把新空間全部置0,並檢查操作是否成功。這樣,你只需要告訴它需要多少空間就行了,還減少了程序出錯的可能。
 

//: C03:Getmem.h

// Function template for memory

#ifndef GETMEM_H

#define GETMEM_H

#include "../require.h"

#include <cstdlib>

#include <cstring>

template<class T>

void getmem(T*& oldmem, int elems)

{

typedef int cntr; // Type of element counter

const int csz = sizeof(cntr); // And size

const int tsz = sizeof(T);

if(elems == 0)

{

free(&(((cntr*)oldmem)[-1]));

return;

}

T* p = oldmem;

cntr oldcount = 0;

if(p)

{

// Previously allocated memory

// Old style:

// ((cntr*)p)--; // Back up by one cntr

// New style:

cntr* tmp = reinterpret_cast<cntr*>(p);

p = reinterpret_cast<T*>(--tmp);

oldcount = *(cntr*)p; // Previous # elems

}

T* m = (T*)realloc(p, elems * tsz + csz);

require(m != 0);

*((cntr*)m) = elems; // Keep track of count

const cntr increment = elems - oldcount;

if(increment > 0)

{

// Starting address of data:

long startadr = (long)&(m[oldcount]);

startadr += csz;

// Zero the additional new memory:

memset((void*)startadr, 0, increment * tsz);

}

// Return the address beyond the count:

oldmem = (T*)&(((cntr*)m)[1]);

}

 

template<class T>

inline void freemem(T * m) { getmem(m, 0); }

#endif // GETMEM_H ///:~

 

爲了能夠清空新的內存空間,程序分配了一個計數器來記錄有多少個內存塊被分配了,typedef cntr就是這個計數器的類型。

有一個指針的引用(oldmem)非常關鍵,因爲在我們分配新內存空間的時候,原來的內存頭指針就改變了,這個可以幫我們找回頭指針。

如果參數傳遞的是0,這塊內存就被釋放掉,這是附加功能freemem()所借用的。

你會發現getmem的操作是相當底層的,這裏有許多類型和字節的操作,例如,指針oldmem並沒有指向內存的開始空間,它把內存的起始空間讓給計數器使用。所以,當我們要free()這塊內存,getmem()必須倒退這個指針cntr所佔用的字節數,因爲oldmem是一個T*,它必須首先被轉換成cntr*,然後索引倒退一個位置,最後在該地址執行free():
 

free(&(((cntr*)oldmem)[-1]));
 

類似的,如果預先分配過內存,getmem()也必須先拿到目前內存的分配情況,然後再重新計算調用realloc()的方法。如果尺寸增加了,爲了清空新的地址空間,我們就必須算出,使用memset的起始地址,最後,oldmem的地址依然是越過計數器的地址空間。
 

oldmem = (T*)&(((cntr*)m)[1]);
 

重申:因爲oldmem是一個對指針的引用,它將可以改變外界傳進來的任何參數

這裏有一個測試getmem()的程序:
 

//: C03:Getmem.cpp

// Test memory function template

#include "Getmem.h"

#include <iostream>

using namespace std;

int main()

{

int* p = 0;

getmem(p, 10);

for(int i = 0; i < 10; i++)

{

cout << p[i] << ' ';

p[i] = i;

}

cout << '\n';

getmem(p, 20);

for(int j = 0; j < 20; j++)

{

cout << p[j] << ' ';

p[j] = j;

}

cout << '\n';

getmem(p, 25);

for(int k = 0; k < 25; k++)

cout << p[k] << ' ';

freemem(p);

cout << '\n';

float* f = 0;

getmem(f, 3);

for(int u = 0; u < 3; u++)

{

cout << f[u] << ' ';

f[u] = u + 3.14159;

}

cout << '\n';

getmem(f, 6);

for(int v = 0; v < 6; v++)

cout << f[v] << ' ';

freemem(f);

} ///:~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章