http://blog.csdn.net/shendl/archive/2008/01/12/2040031.aspx
模板概述
泛型是C++中的重要特性。據(jù)說(shuō),已經(jīng)在C++社區(qū)中已經(jīng)取代面向?qū)ο蟪蔀镃++的主要編程泛型。STL和boost庫(kù)等都廣泛使用了泛型。 泛型,就是C++的模板機(jī)制。 模板可以看作是C++宏的衍生。宏,就相當(dāng)于是文本文件中的替換。C++編譯器在編譯前,先把所有使用宏的地方,用宏的定義替換掉宏。 在Java,.net,ruby等現(xiàn)代語(yǔ)言中都沒(méi)有宏這種語(yǔ)法的地位。 宏是另程序變得晦澀難懂的一個(gè)原因!我認(rèn)為在程序中應(yīng)該盡量避免使用宏! 模板也可以看作是一種模板。C++編譯器在編譯之前,將創(chuàng)建模板的具體類(lèi)型的源代碼,然后再編譯成二進(jìn)制代碼。 模板技術(shù) 模板類(lèi)的聲明和定義,形如: template<typename T> class Manage{…全部?jī)?nèi)聯(lián)函數(shù)實(shí)現(xiàn)!};
函數(shù)模版的定義,形如: template<typename SequenceT>
void trim(SequenceT &, const std::locale & = std::locale());
模板的特化 模板類(lèi)的特化 1)首先定義基泛型: template<typename T> class Manage{…全部?jī)?nèi)聯(lián)函數(shù)實(shí)現(xiàn)!};
2)然后定義特化的泛型: #include 上面基泛型的文件 template<> class Manage<B>
{…全部?jī)?nèi)聯(lián)函數(shù)實(shí)現(xiàn)!};
特化的泛型必須自己實(shí)現(xiàn)所有基泛型定義的成員函數(shù)和靜態(tài)成員。 模板類(lèi)的成員函數(shù)的特化 如果我們希望特化的泛型繼承絕大部分的基泛型的代碼。 那么只需定義特化的成員函數(shù)即可! 在基泛型的定義后面加上特化成員函數(shù): template<>
void Manage<B>::sayHello(void){
cout<<"B"<<this->t<<endl;
};
這個(gè)特化的函數(shù)就是特化模板類(lèi)的成員函數(shù)。 實(shí)際上,這相當(dāng)于是隱式定義了上面的那樣一個(gè)特化模板類(lèi),并且所有的基本實(shí)現(xiàn)使用基泛型模板的實(shí)現(xiàn)! 偏特化/部分特化 就是一個(gè)模板類(lèi)有多個(gè)泛型參數(shù)。 我們特化一個(gè)模板參數(shù): 1)基泛型有多個(gè)模板參數(shù): #pragma once
#include "cppunit/extensions/HelperMacros.h"
#include "B.h"
#include <iostream>
using namespace std;
template<typename V,typename T> class Manage
{
private:
T* t;
public:
Manage(void){
this->t=new T();
};
void sayHello(void){
cout<<"管理"<<this->t<<endl;
};
public:
virtual ~Manage(void){};
};
2)定義的特化有一個(gè)還是任意的類(lèi)型參數(shù) #pragma once
#include "Manage.h"
#include "B.h"
#include <iostream>
/*相當(dāng)于
template<typename V,沒(méi)有> class Manage<V,B>
沒(méi)有對(duì)應(yīng)已有的類(lèi)型B
*/
template<typename V> class Manage<V,B>
{
private:
B* t;
public:
Manage(void){
this->t=new B();
}
virtual ~Manage(void){};
public:
void sayHello(void){
std::cout<<"B類(lèi)"<<this->t<<std::endl;
};
};
但是,請(qǐng)注意,半特化,則沒(méi)有特化中對(duì)應(yīng)的成員函數(shù)的特化那種簡(jiǎn)單扼要的形式?。。?br> 模板的使用 使用模板的類(lèi)應(yīng)該寫(xiě)在頭文件中,并以源碼的方式發(fā)布 C++的泛型編程中,需要把所有使用到泛型聲明或者定義的代碼都直接寫(xiě)在.h頭文件中,不能寫(xiě)在.cpp文件中,否則會(huì)有很多奇怪的錯(cuò)誤! VC2005也還沒(méi)有支持分離編譯的export關(guān)鍵字! 模板類(lèi)只能寫(xiě)在一個(gè).h文件中。而且,不可以放在dll項(xiàng)目中。因?yàn)槟0孱?lèi)是無(wú)法導(dǎo)出的! 導(dǎo)出以后的模板類(lèi),只能夠在外部聲明這個(gè)模板類(lèi),不能夠?qū)嶋H創(chuàng)建模板類(lèi)的對(duì)象!否則會(huì)報(bào)告 TestMain.obj : error LNK2019: 無(wú)法解析的外部符號(hào)"__declspec(dllimport) public: __thiscall net_sf_interfacecpp_core_lang::ObjectRefManage<class AClass>::ObjectRefManage<class AClass>(void)" (__imp_??0?$ObjectRefManage@VAClass@@@net_sf_interfacecpp_core_lang@@QAE@XZ),該符號(hào)在函數(shù)_main 中被引用 這樣的錯(cuò)誤。 因?yàn)?,模板?lèi)實(shí)際上并沒(méi)能編譯成二進(jìn)制代碼。它只是一個(gè)宏!需要在編譯時(shí)根據(jù)客戶代碼的使用情況生成源代碼,然后再變成二進(jìn)制代碼。 因此,作為宏,它應(yīng)該在.h文件中。作為源代碼的元數(shù)據(jù),應(yīng)該共享給用戶。因?yàn)樗枰鶕?jù)客戶的使用情況來(lái)生成源代碼。因此,它必須在最終客戶代碼一起! 要使用模板類(lèi),就必須把它單獨(dú)拿出來(lái),把.h這個(gè)頭文件/源代碼交給用戶。 用戶在項(xiàng)目中直接作為源代碼使用這個(gè)頭文件,才能夠使用這個(gè)模板類(lèi)! //確保只被引入系統(tǒng)一次
#ifndef _net_sf_interfacecpp_core_lang_ObjectRefManage_h_
#pragma once
#include "..\net_sf_interfacecpp\IObject.h"
//下面是自定義的所有.cpp文件都需要引入的頭文件
//#include "ConfigApp.h"
#include "..\net_sf_interfacecpp\Object.h"
#pragma comment(lib,"..\\debug\\net_sf_interfacecpp.lib")
/*
用于管理任意類(lèi)的實(shí)例的生命周期,使之符合IObject接口
模板類(lèi)必須定義在頭文件中
NET_SF_INTERFACECPP_API
*/
namespace net_sf_interfacecpp_core_lang{
template<typename T>
class ObjectRefManage:public IObject
{
private:
IObject* pIObject;
T* pT;
//copy構(gòu)造函數(shù)
ObjectRefManage(const ObjectRefManage &that);
//重載等于操作符
ObjectRefManage& operator=(const ObjectRefManage &that);
//void operator delete(ObjectRefManage* thisPtr);
public:
T* getObjectPtrAndAddRef(){
this->addRef();
return this->pT;
};
T* getObjectPtrNotAddRef(){
return this->pT;
};
ObjectRefManage(void){
//現(xiàn)在引用是
this->pIObject=new Object();
this->pT=new T();
};
long addRef(){
return this->pIObject->addRef();
};
long release(){
long result=this->pIObject->release();
if(result==0){
delete this->pT;
delete this;
return 0;
}
};
void setSingleton(){
this->pIObject->setSingleton();
};
public:
virtual ~ObjectRefManage(void){};
};
}
//確保只被引入系統(tǒng)一次
#define _net_sf_interfacecpp_core_lang_ObjectRefManage_h_
#endif
dll依賴模板時(shí)使用方式 1)模板依賴于我們的dll 2)如果我們的類(lèi)需要使用這個(gè)模板,就需要另外建一個(gè)dll—ext.dll,包括這個(gè)模板,從而間接包括核心dll。 Dll內(nèi)部時(shí)可以使用模板的,因?yàn)榭梢灾苯釉谏蒬ll時(shí)根據(jù)內(nèi)部的使用模板的情況,創(chuàng)建源代碼,編譯成dll。 但是,如果把dll內(nèi)部的模板發(fā)布出去,這就不行了! 3)這個(gè)模板頭文件和dll必須同時(shí)提供,避免找不到模板依賴的dll而出錯(cuò)! 對(duì)模板參數(shù)沒(méi)有限制是一大誤區(qū) 考察STL和boost中使用泛型的例子。我發(fā)現(xiàn)一個(gè)問(wèn)題。使用模板的類(lèi),在使用時(shí),程序員可以指定任何類(lèi)和基本類(lèi)型。 但是,實(shí)際上,很多模板類(lèi)在代碼的內(nèi)部實(shí)現(xiàn)中,對(duì)參數(shù)類(lèi)型能夠提供的操作實(shí)際上是有要求的。如,需要>,<,=等操作是有意義的。 或者需要能夠調(diào)用某個(gè)方法。 但是,STL和boost的庫(kù)中,均沒(méi)有對(duì)參數(shù)進(jìn)行限制! 這樣,如果客戶程序員使用了錯(cuò)誤的參數(shù)類(lèi)型,那么程序還是能夠正常編譯。只有在運(yùn)行到這段代碼時(shí),才會(huì)報(bào)錯(cuò)。 甚至,由于STL和boost喜歡使用操作符重載,因此,即使運(yùn)行時(shí),也不會(huì)出錯(cuò),只是真正的邏輯錯(cuò)了。這樣的問(wèn)題,怎么才能找到錯(cuò)誤點(diǎn)呢?我不禁倒吸了一口涼氣! 翻開(kāi)C++之父BS的《C++語(yǔ)言的設(shè)計(jì)與演化》一書(shū),BS本人對(duì)模板的這一描述,令我乍舌! BS居然認(rèn)為不需要限制模板的參數(shù)類(lèi)型。認(rèn)為對(duì)模板參數(shù)的限制是OOP程序員的偏見(jiàn)! 暈!C++是靜態(tài)編譯型語(yǔ)言,不是ruby,python,JavaScript這樣的動(dòng)態(tài)面向?qū)ο笳Z(yǔ)言。 如果ruby開(kāi)發(fā)中,你用了錯(cuò)誤類(lèi)型的對(duì)象,執(zhí)行時(shí)沒(méi)有報(bào)錯(cuò),直到你運(yùn)行到這段代碼才報(bào)錯(cuò),那我也沒(méi)什么話好說(shuō)的。人家是解釋型語(yǔ)言,放棄了編譯檢查錯(cuò)誤,但換來(lái)了語(yǔ)言的巨大動(dòng)態(tài)靈活性。有所得必有所失嘛!這我就不說(shuō)它了! 但BS認(rèn)為C++不應(yīng)該限制模板的參數(shù)類(lèi)型,聽(tīng)任錯(cuò)誤在運(yùn)行時(shí)爆發(fā),就讓我無(wú)法理解了! BS,不能因?yàn)槟銓?duì)模板的偏愛(ài),讓這么多C++程序陷入危險(xiǎn)啊! 通過(guò)派生對(duì)模板的參數(shù)類(lèi)型加以限制的一種方法。 形如: Template <typename T> class Compare{}; Template <typename T: Compare > class Vector{}; BS認(rèn)為不應(yīng)該采用這種方式。 在java中使用模板時(shí),我們經(jīng)常使用這種方式。 如: Public MyClass<E extends String>{……} 但,BS認(rèn)為這種方式不好。而且我在VS2005中也無(wú)法編譯這樣的代碼。 確實(shí),這樣會(huì)讓模板類(lèi)的數(shù)量直線上升。 第二種BS提到的方法非常丑陋。 就是讓每一個(gè)方法的實(shí)現(xiàn)都轉(zhuǎn)換成我們需要的類(lèi)型。這樣編譯時(shí)就會(huì)報(bào)錯(cuò)。 第三種方法,就是使用模板的特化,或者叫做專(zhuān)門(mén)化。 這是BS推薦使用的方法。我也認(rèn)為應(yīng)該使用模板特化來(lái)限制模板的參數(shù)類(lèi)型。 盡管BS提出這種語(yǔ)法的本意并不是用來(lái)限制模板的參數(shù)類(lèi)型。 因?yàn)?,BS根本就不認(rèn)為應(yīng)該限制模板的參數(shù)類(lèi)型。偏執(zhí)的家伙! 使用模板特化限制模板的參數(shù)類(lèi)型 作為一個(gè)堅(jiān)定的OO程序員,我是不會(huì)容許在自己的C++程序中像STL和boost那樣,允許任意參數(shù)類(lèi)型隨意使用我的模板類(lèi)的! BS的觀點(diǎn),我不能茍同! 我認(rèn)為,可以使用模板特化限制模板的參數(shù)類(lèi)型。這種辦法是最簡(jiǎn)單有效的。 首先,我們定義一個(gè)基范型。 然后再在基范型模板類(lèi)的外部定義幾個(gè)重載的方法。 指定如果是我們需要的參數(shù)類(lèi)型,應(yīng)該執(zhí)行這些方法。 也可以獨(dú)立定義特化的模板類(lèi)。但是,我們上面已經(jīng)說(shuō)過(guò)了,特化模板類(lèi),不如特化模板類(lèi)的成員函數(shù)合算! 最后,我們?cè)诨缎偷膶?shí)現(xiàn)中,拋出一個(gè)自定義的異常。這樣,如果使用了錯(cuò)誤的類(lèi)型,就會(huì)拋出異常,導(dǎo)致系統(tǒng)停止運(yùn)行。我們的客戶就可以發(fā)現(xiàn)問(wèn)題所在。 當(dāng)然,編譯時(shí),即使是不正確的類(lèi)型,還是能夠編譯通過(guò)。只有在運(yùn)行時(shí)才會(huì)把錯(cuò)誤抓出來(lái)。 編譯時(shí)檢查不出錯(cuò)誤,這只能怪BS和C++標(biāo)準(zhǔn)委員會(huì)沒(méi)有為我們提供限制模板的參數(shù)類(lèi)型的語(yǔ)法了。 補(bǔ)充:C++的模板和java的模板的異同
Java5中,也引入的泛型語(yǔ)法。如:
Public MyClass<E extends String>{ …… } 看上去類(lèi)似,但是實(shí)際實(shí)現(xiàn)卻非常不同。 C++的模板,是會(huì)在編譯時(shí),先生成很多新的C++類(lèi)。因此,C++中使用模板有一個(gè)問(wèn)題,就是模板生成的源代碼可能太多。引起編譯的性能問(wèn)題。 而java的模板實(shí)現(xiàn)機(jī)制完全不同。Java模板類(lèi)在編譯時(shí),會(huì)“擦除”類(lèi)型信息。 不會(huì)生成新的java類(lèi)的源代碼。 因?yàn)?,java的類(lèi)繼承體系是單根的,所有類(lèi)都是Object類(lèi)的子類(lèi)。因此,在Java5之前,沒(méi)有引入模板這個(gè)語(yǔ)法之前,java和它的集合實(shí)現(xiàn)類(lèi)也過(guò)得很滋潤(rùn)。 Java模板類(lèi)在編譯時(shí),我猜想是這樣子的: 1,首先,擦除模板類(lèi)型的信息,還是使用原來(lái)的Object類(lèi)型。 2,在所有使用模板的參數(shù)類(lèi)型的地方,加上強(qiáng)制類(lèi)型轉(zhuǎn)換,轉(zhuǎn)換成程序員指定的模板參數(shù)類(lèi)型。 我特別記得BS的一句話,認(rèn)為特有道理: 他在C++中特別把不應(yīng)該使用的語(yǔ)法設(shè)計(jì)得丑陋,讓你不想去使用。如: dynamic_cast < type-id > ( expression ) 動(dòng)態(tài)類(lèi)型轉(zhuǎn)換。 BS認(rèn)為,顯式的類(lèi)型轉(zhuǎn)換通常是不必要的。應(yīng)該避免。 我深深地贊同這句話。Java引入模板,應(yīng)該就是為了這個(gè)原因?,F(xiàn)在,寫(xiě)Java代碼可以少用很多強(qiáng)制類(lèi)型轉(zhuǎn)換! 用錯(cuò)模板的參數(shù)類(lèi)型,Java編譯器都會(huì)準(zhǔn)確地報(bào)告錯(cuò)誤。 唉,C++的模板要是也這樣就好了! 本文來(lái)自CSDN博客,轉(zhuǎn)載請(qǐng)標(biāo)明出處:http://blog.csdn.net/shendl/archive/2008/01/12/2040031.aspx (#)
|
|