2009年5月20日 星期三

c++ template學習筆記

雖然平常程式也寫不少,但是template倒是一直沒什麼機會去深究
今天機會難得就把一些學習心得po上來了

template用法非常多樣,多到可以再出一本書
但是不外乎幾個基本用法

Function Template (函數樣板)


只針對函式寫樣板

template <class T>
void swaps(T& a1,T& a2){
T tmp=a1;
a1=a2;
a2=tmp;
}
int main() {
int a=10;
int b=20;
cout<<'('<<a<<','<<b<<')'<<endl;
swaps(a,b);
cout<<'('<<a<<','<<b<<')'<<endl;
return 0;
}
//output:
(10,20)
(20,10)

也有人會把template跟函式名稱寫在一起

template <class T> void swaps(T& a1,T& a2){...}


這邊有一點要注意的就是把函式的宣告跟實作分開時,必須上下都加template

template <class T>
void swaps(T& a1,T& a2);
...
void swaps(T& a1,T& a2){
T tmp=a1;
a1=a2;
a2=tmp;
}

像上面一段程式碼會編譯不過
必須修改成

template <class T>
void swaps(T& a1,T& a2);
...
template <class T>
void swaps(T& a1,T& a2){
T tmp=a1;
a1=a2;
a2=tmp;
}

簡單來說只要有用到樣板的地方都該加上template <class T>


T是由自己命名,叫什麼都可以,只要能跟叫用的函式對起來即可


這邊補充一點,樣板有個功能叫特製化(Specialization)
Specialization
同樣名稱的樣板函式或類別可以重複出現,只要針對某個type進行特製化
這是為了有時候我們需要對某些Type作"特別"的處理

語法如下
template <> Return function<CType>{}
就像這樣template裏面保持為空,針對CType作特別處理


template <class T>
void swaps(T& a1,T& a2);

template <>
void swaps<int>(int& a1,int& a2){//特製化樣板 如果傳近來int就什麼都不做

}

int main() {
int a=10;
int b=20;
cout<<"特製化樣板"<<endl;
cout<<'('<<a<<','<<b<<')'<<endl;
swaps<int>(a,b);
cout<<'('<<a<<','<<b<<')'<<endl;
float c=10;
float d=20;
cout<<"一般樣板"<<endl;
cout<<'('<<c<<','<<d<<')'<<endl;
swaps<float>(c,d);
cout<<'('<<c<<','<<d<<')'<<endl;
return 0;
}
template <class T>
void swaps(T& a1,T& a2){
T tmp=a1;
a1=a2;
a2=tmp;
}
//output:
特製化樣板
(10,20)
(10,20)
一般樣板
(10,20)
(20,10)

然後還有一種就是可以帶參數的樣板

template<class T,int Size> //帶一個參數
void print(T t)
{
cout<<t<<":"<<Size<<endl;
}
int main() {
int data=100;
print<int,32767>(data);
return 0;
}
//output:
100:32767



Class Template (類別樣板)


替類別設置樣板,其有效範圍是整個"類別的宣告"

#include <iostream>
using namespace std;

template <class T>
//Printer.h
class Printer {
T _t;
public:
Printer(T t){_t=t;}
void print(){
cout<<_t<<endl;
}

};
...
//main.cpp
int main() {
Printer<int> p(100);
p.print();
return 0;
}
//output:
100

這邊有個小語病,其有效範圍是整個"類別的宣告"
是的,出了這個宣告仍然要加上template的宣告


//Printer.h
template <class T>
class Printer {
T _t;
public:
Printer(T t);
void print();

};

Printer::Printer(T t){
_t=t;
}

void Printer::print(){
cout<<_t<<endl;
}

像這樣直接把宣告跟實作拉開是不行的,會編譯不過
必須修改如下

//Printer.h
template <class T>
class Printer {
T _t;
public:
Printer(T t);
void print();

};

template <class T>
Printer<T>::Printer(T t){
_t=t;
}
template <class T>
void Printer<T>::print(){
cout<<_t<<endl;
}

如上,除了template要宣告,把實作分開時原本的Printer::的呼叫方式也行不通了
必須改成型別 Printer<T>::
初使化靜態變數也一樣

template <class T>
class Printer {
static int count;
};

template <class T>
int Printer<T>::count=0;//初使化靜態變數




這裡再提一個比較重要的地方
template是不能分開成.h和.cpp
如果要把實作分開也要全部再.h檔裏面完成

//Printer.h
template <class T>
class Printer {
static int count;
};


//Printer.cpp
template <class T>
int Printer<T>::count=0;//初使化靜態變數

像上面這樣一支程式是編譯不會通過的
不過有的編譯器或IDE很聰明,可以解決這問題
但是保守之道還是寫再.h檔比較保險

而樣板類別當然也能進行特製化
舉個『The C++ Programming Language, Special Edition』上的例子

template <class T>
class Vector{
T* v;
int sz;
public:
static int count;
Vector(int size);
~Vector();
T& elem(int i){return v[i];}
T& operator[](int i);
};

這是一個Vector類別,能接收任何型態
但是我覺得不夠,我希望能對指標做處理於是新增了一個

template<> class Vector<void*>{
void** p;
public:
Vector(int size){
p=new void*[size];
}
~Vector(){
}
void*& elem(int i){return p[i];}
void*& operator[](int i){return p[i];}
};

上面是一個萬用指標void*為基底的Vector 他並不會真正給user使用
這邊稍微提一下void*& 這種返回型態
因為我們希望可以把他當一般陣列一樣使用

Vector<int*> v;
*v[3]=100;

因此他必須回傳一個lvalue,要讓return的值變成lvalue的方式就是加上&
如果沒加上直接用

void* operator[](int i){return p[i];}
...
Vector<int*> v;
*v[3]=100;

就會出現如下的錯誤訊息

error: lvalue required as left operand of assignment


回到正題,如果想用指標型態還比須做一個動作
新增一個真正給user用的類別
再次宣告一個特製化類別<T*>,此時他的template不為空<>
因為他是為了T延伸的特製化T* 所以必須加上去


template<class T> class Vector<T*>:private Vector<void*>{
public:
typedef Vector<void*> Base;
explicit Vector(int size):Base(size){
}
~Vector(){
}
T*& operator[](int i){
static T* t=(T*)Base::elem(i);
return t;
}
};

這樣就沒問題了
上面有個點T*& operator[](int i),為何我用個靜態變數去傳呢
我照範例上面直接傳是不行,因為void*無法直接的轉成T*
如果我用

T*& operator[](int i){
return Base::elem(i);
}

會出現如下的錯誤訊息

invalid initialization of reference of type float*& from expression of type void*

而如果我照書上的寫法

T*& operator[](int i){
return static_cast<T*&>(Base::elem(i));
}

則又會出現如下的錯誤訊息

invalid static_cast from type void* to type int*&

而如果,我只試想轉型呢?


T*& operator[](int i){
return (T*)Base::elem(i);
}

結果還是不行,出現了錯誤訊息

invalid initialization of non-const reference of type char*& from a temporary of type char*

因為經過轉型後他會回傳一個(暫存變數)temporary,但是T*&這個型態因為&的關性他再等一個不會被消滅的變數
後來我就想了想,用個local變數如何

T*& operator[](int i){
T* t=(T*)Base::elem(i);
return t;
}

結果如何呢?當然還是不行,以lvalue當回傳值,不可能去接一個local變數,因為出了這個函式他就掛了,會出現如下的錯誤訊息

warning: reference to local variable t returned

最後絕招,加上static修飾,改變local的lifetime,讓他從stack移到.Data去

T*& operator[](int i){
static T* t=(T*)Base::elem(i);
return t;
}

這樣總算公德圓滿了

沒有留言: