1. 程式人生 > 其它 >非型別模板引數如何使用&非型別模板引數使用時的注意事項&如何控制模板的例項化以節省記憶體空間

非型別模板引數如何使用&非型別模板引數使用時的注意事項&如何控制模板的例項化以節省記憶體空間

技術標籤:C++ 模板超程式設計編譯器c++字串

非型別模板引數

含有非型別模板引數的函式在過載時的注意事項

形式一:

#include<iostream>
usingnamespacestd;
#include<vector>
#include<algorithm>

template<typenameT,intval>
TAddValue(Tconst&obj)
{
returnobj+val;
}

intmain()
{
vector<int>Vector_Obj1{1,2,3,4,5,6},Vector_Obj2;
Vector_Obj2.resize(Vector_Obj1.size());
transform(Vector_Obj1.begin(),Vector_Obj1.end(),Vector_Obj2.begin(),(int(*)(intconst&))AddValue<int,5>);
for_each(Vector_Obj2.begin(),Vector_Obj2.end(),[](constint&obj){cout<<obj<<"";});
}

形式二:

#include<iostream>
usingnamespacestd;
#include<vector>
#include<algorithm>

template<typenameT,intval>
TAddValue(Tconst&obj)
{
returnobj+val;
}

intmain()
{
vector<int>Vector_Obj1{1,2,3,4,5,6},Vector_Obj2;
Vector_Obj2.resize(Vector_Obj1.size());
transform(Vector_Obj1.begin(),Vector_Obj1.end(),Vector_Obj2.begin(),AddValue<int,5>);
for_each(Vector_Obj2.begin(),Vector_Obj2.end(),[](constint&obj){cout<<obj<<"";});
}

我們看到上述兩種形式,最大的區別就在於:

①transform(Vector_Obj1.begin(), Vector_Obj1.end(), Vector_Obj2.begin(), (int(*)(int const&))AddValue<int, 5>);

② transform(Vector_Obj1.begin(), Vector_Obj1.end(), Vector_Obj2.begin(), AddValue<int, 5>);

我們有時就會產生疑問:這兩種程式碼輸出結果相同,但為什麼形式一中要進行函式指標的強制型別轉換呢?

在《C++ templates》中有這樣一段話,引人深思:

這段話的意思是說:

AddValue<int,5>相當於一個函式指標,對於C++重載出的很多同名但是引數不同的函式來說,單純的看AddValue這個獨立的函式指標不看引數型別,我根本不知道AddValue這個函式指標指向函式的那個過載版本。雖然編譯器可以在Transform中通過觀察判斷傳遞給AddValue函式指標的引數可以判斷出是呼叫了那個函式過載版本,但是我們不清楚呀!因此我們要進行強制型別轉換,雖然強轉與否沒啥卵用,但是至少可以使我們思路清晰,直到這個函式的到底呼叫了那個函式過載版本去操作的這一堆資料。

舉例說明:

#include<iostream>
usingnamespacestd;
#include<vector>
#include<algorithm>

template<typenameT,intval>
TAddValue(Tconst&obj)
{
returnobj+val;
}
template<typenameT,intval>
doubleAddValue(doubleconst&obj)
{
return(obj+(double)val)/2;
}

intmain()
{
vector<int>Vector_Obj1{1,2,3,4,5,6};
vector<double>Vector_Obj2;
Vector_Obj2.resize(Vector_Obj1.size());
transform(Vector_Obj1.begin(),Vector_Obj1.end(),Vector_Obj2.begin(),(double(*)(doubleconst&))AddValue<int,5>);
for_each(Vector_Obj2.begin(),Vector_Obj2.end(),[](constdouble&obj){cout<<obj<<"";});
}

這個例子中,強制型別轉換就很有必要了,沒有了強制型別轉換,編譯器根本僅僅根據一個光溜溜的函式指標根本識別不出到底要執行那個函式過載版本,當編譯器一出現徘徊不定的現象時,錯誤警告就接踵而至:

非型別模板的引數限制

不可以是浮點型型別的量(變數/常量):

匹配模板特化時,編譯器會匹配模板引數,包括非型別引數。

就其本質而言,浮點值並不精確,並且C ++標準未指定它們的實現。因此,很難確定兩個浮點非型別引數何時真正匹配:

template <float f> void foo () ;void bar () {

 foo< (1.0/3.0) > ();

foo< (7.0/21.0) > ();

}

這些表示式不一定產生相同的“位模式”,因此不可能保證它們使用相同的特化 - 沒有特殊的措辭來涵蓋這一點。

非型別模板引數不能使用內部連結物件(例如string)作為實參

當我們在一個專案的多個原始檔中使用相同的string字串例項化非型別引數模板時,按理說相同的string字串例項化出來的非型別引數模板應該是相同的,但是別忘了,我們例項化非型別引數模板是在不同的原始檔中,不同原始檔中的string字串常量被儲存在不同區域中,不同區域的資料例項化出來的非型別引數模板對於編譯器來說是不相同的,這就有些可怕了,在我們看來相同的string字串應該例項化出相同的模板才對,但是由於相同的string字串定義檔案不同,導致最終例項化出的模板不相同,針對於這種情況,編譯器才給出了“非型別模板引數的形參不可以使用內部連結物件”。

如何使用string型別作為非型別模板引數呢?(改變連結屬性)

C++規定,有const修飾的變數,不但不可修改,還都將具有內部連結屬性,也就是隻在本檔案可見。(這是原來C語言的static修飾字的功能,現在const也有這個功能了。)又補充規定,extern const聯合修飾時,extern將壓制const這個內部連結屬性。於是,extern const string將仍然有外部連結屬性,但是還是不可修改的。

showing.hpp

#include<iostream>
#include<string>
usingnamespacestd;

template<conststring&obj>
voidShowInf()
{
cout<<obj<<endl;
}

main.cpp

#include<iostream>
#include"ShowInf.hpp"
usingnamespacestd;

externconststringstr="helloworld!";

intmain()
{
ShowInf<str>();
}

​​​​​​​

輸出結果:

注意:extern只能存在於main.cpp中函式體的外部,以下情形是不對的:

#include<iostream>
#include"ShowInf.hpp"
usingnamespacestd;

intmain()
{
externconststringstr="helloworld!";
ShowInf<str>();
}

Const char*變數為什麼不可以呢?

你得用下述的方式傳遞const char陣列:

Main.cpp

#include<iostream>
#include<string>
#include"Person.hpp"
#include"ShowInf.hpp"
usingnamespacestd;

constchars[]="hello";// 加不加extern都OK

intmain()
{
ShowInf<s>();
}

ShowInf.hpp

#include<iostream>
#include<string>
usingnamespacestd;

template<constchar*obj>
voidShowInf()
{
cout<<obj<<endl;
}

注意:變數s一定要宣告為:const char s[] = "hello",不要宣告為const char* s = “hello”!

那用上述方式可不可以使用類型別作為非型別模板的實參呢?

我做了如下嘗試,得出的答案是:不可以。

因為類型別是不可以作為編譯期常量的。其實,你可以這樣的巧記:類型別是多個基本資料型別的有機結合,float,double浮點型況且都不可以作為非型別模板的引數,類型別怎麼又可以擔當這個大任呢!

Person.hpp

#include<iostream>
#include<string>
usingnamespacestd;

classPerson
{
private:
intage;
stringname;
public:
Person(intage,stringname)
{
this->age=age;
this->name=name;
}
voidDefine(intage,stringname)
{
this->age=age;
this->name=name;
}
friendostream&operator<<(ostream&ostr,Person&obj);
};

ostream&operator<<(ostream&ostr,Person&obj)
{
ostr<<obj.name<<"的姓名為"<<obj.age;
returnostr;
}

Showing.hpp

#include<iostream>
#include<string>
usingnamespacestd;

template<constPerson&obj>
voidShowInf()
{
cout<<obj<<endl;
}

Main.cpp

#include<iostream>
#include<string>
#include"Person.hpp"
#include"ShowInf.hpp"
usingnamespacestd;

externPerson&obj;

intmain()
{
obj.Define(19,"張三");
ShowInf<obj>();
}

​​​​​​​

輸出錯誤

什麼是內部連結和外部連結?

內部連結就是該符號只在編譯單元內有效,其他編譯單元看不到。所以 多個編譯單元中可有相同符號。const變數可以出現在多個.cpp檔案中 而不會衝突就是因為是內部連結。

外部連結就是其他編譯單元能看到當前編譯單元的符號。如果有相同的外部連結符號,就會在連結時報重定義符號的錯誤。

對於引數為型別的模板,相同型別例項化出的模板會由於所屬檔案不同而被儲存在不同儲存區域,這對於我們寶貴的記憶體來說是一種非常囂張的浪費,我們應該如何避免這種鋪張浪費的情況呢?

模板例項化:

當模板被使用時才會例項化,這一特性意味著,相同的例項可能出現在多個物件檔案中。舉個例子就是說,當兩個或多個獨立編譯的原始檔使用了相同的模板,並提供了相同的模板引數時,每個檔案中都會有該模板適用該引數的一個例項。

上述的問題在小程式裡不算什麼,但是在一個大的程式中,在多個檔案中例項化相同模板的額外開銷會非常嚴重。在新標準中,我們可以通過控制顯式例項化來避免這種開銷。

控制例項化

與多檔案中宣告一個變數相同,可以使用關鍵字extern來承諾在程式其他位置會有一個該例項化的非extern宣告,只需要在連結成可執行程式時將含有例項化的.o檔案連結上就可以了。因此使用關鍵字extern進行一個模板的宣告時不會在本檔案中生成例項化程式碼。

如下演示一下具體如何操作:

第一個檔案主要用來定義模板(當然這裡的第一個檔案代指了我們在專案中定義的許多模板檔案):

// Stack.hpp
#include<iostream>
usingnamespacestd;
#include<vector>

//模板的預設值
template<classT,classCONT=vector<T>>
classStack
{
private:
CONTelement;
intSize;
public:
Stack();
Stack(Tobj);
Stack(vector<T>obj);

voidPush(Tobj);
voidPop();
T&Top();
boolEmpty();
intPrintSize();
T&At(intorder);
};

template<classT,classCONT/*=vector<T>*/>
T&Stack<T,CONT>::At(intorder)
{
if(order>=this->Size)
{
throwout_of_range("overarrayrange!");
}
returnthis->element.at(order);
}

template<classT,classCONT/*=vector<T>*/>
intStack<T,CONT>::PrintSize()
{
returnthis->Size;
}

template<classT,classCONT/*=vector<T>*/>
boolStack<T,CONT>::Empty()
{
returnthis->element.empty();
}

template<classT,classCONT/*=vector<T>*/>
T&Stack<T,CONT>::Top()
{
if(this->Size==0)
{
throwout_of_range("Stackisempty!");
}
return*(this->element.end());
}

template<classT,classCONT/*=vector<T>*/>
voidStack<T,CONT>::Pop()
{
if(this->Size==0)
{
throwout_of_range("Stackisempty!");
}
this->element.pop_back();
this->Size--;
}

template<classT,classCONT/*=vector<T>*/>
voidStack<T,CONT>::Push(Tobj)
{
this->element.push_back(obj);
this->Size++;
}

template<classT,classCONT/*=vector<T>*/>
Stack<T,CONT>::Stack(vector<T>obj)
{
this->Size=0;
this->element.clear();

this->element=obj;
this->Size=obj.size();
}

template<classT,classCONT/*=vector<T>*/>
Stack<T,CONT>::Stack(Tobj)
{
this->Size=0;
this->element.clear();

this->element.push_back(obj);
this->Size++;
}

template<classT,classCONT/*=vector<T>*/>
Stack<T,CONT>::Stack()
{
this->Size=0;//賦值前一定要初始化
this->element.clear();
}

在第二個檔案中主要定義我們要在專案中使用到的所有例項化模板:

// TemplateBuild.cpp
#include"Stack.hpp"
#include<iostream>
#include<string>
usingnamespacestd;

templateStack<string>;//加不加template關鍵字都OK
templateStack<int>;

第三個檔案中通過外部連結我們可以使用之前已經定義好的例項化模板:

// TemplateApplication.cpp
#include"Stack.hpp"
#include<iostream>
#include<string>
usingnamespacestd;

externtemplateStack<string>;

intmain()
{
Stack<string>Stack_Obj1("張三");
cout<<Stack_Obj1.At(0)<<endl;
}

​​​​​​​

在編譯時,一個專案中的每個.cpp檔案(TemplateApplication.cpp & TemplateBuild.cpp)都將會編譯成相應的.o檔案,編譯器編譯完後的下一步工作就是根據“外部連結關鍵字extern”將專案中得到的每個.o檔案(TemplateApplication.o & TemplateBuild.o)連結到一起,生成可執行檔案。

注意:由於我們使用外部連結,連結外部的已經例項化的模板並且使用,因此編譯器需要例項化模板的所有成員,這與我們普通的模板例項化不同(不同模板例項化中,用到啥成員就例項化啥成員),這種例項化模板的方式會例項化模板的所有成員。

非型別引數模板的形參要求

非型別模板引數是有限制的,通常是常整數(包括列舉值)或者指向外部連結物件的指標/引用。

模板引數:常整數(int,short,long……可在編譯器確定的整數資料型別)

#include<iostream>
usingnamespacestd;

template<unsignedintN>
voidShowInf()
{
cout<<N<<endl;
}

intmain()
{
constinta=10;
ShowInf<a>();
}

模板引數:指向外部連結物件的指標/引用

#include<iostream>
usingnamespacestd;

template<typenameT,T(*Func)(constT&val1,constT&val2)>
TOperator(Tpara1,Tpara2)
{
returnFunc(para1,para2);
}

intAdd(constint&para1,constint&para2)
{
returnpara1+para2;
}

intmain()
{
intpara1=9,para2=10;
cout<<Operator<int,Add>(para1,para2)<<endl;
}

​​​​​​​

上述程式碼展示了,模板引數為函式指標時的程式碼書寫格式。

模板引數:模板

#include<iostream>
usingnamespacestd;

template<typenameT>
classPrint
{
public:
voidoperator()(constT&val)
{
cout<<val<<"";
}
};

template<typenameT>
classInc
{
public:
voidoperator()(T&val)
{
val++;
}
};

template<typenameT>
classDec
{
public:
voidoperator()(T&val)
{
val--;
}
};

template<typenameT,template<typenameT>classFunc>
voidForeach(TArray[],unsignedsize)
{
Func<T>FuncA;
for(inti=0;i<size;i++)
{
FuncA(Array[i]);//[]為陣列索引符號
}
cout<<endl;
}

intmain()
{
intArray[]={1,2,3,4,5,6,7,8,9,10};
Foreach<int,Inc>(Array,10);
Foreach<int,Print>(Array,10);
Foreach<int,Dec>(Array,10);
Foreach<int,Print>(Array,10);
}

執行結果: