中文字幕日韩精品一区二区免费_精品一区二区三区国产精品无卡在_国精品无码专区一区二区三区_国产αv三级中文在线

C++模板元編程詳細(xì)教程(之三)-創(chuàng)新互聯(lián)

先序文章請(qǐng)看
C++模板元編程詳細(xì)教程(之一)
C++模板元編程詳細(xì)教程(之二)

創(chuàng)新互聯(lián)建站服務(wù)項(xiàng)目包括安平網(wǎng)站建設(shè)、安平網(wǎng)站制作、安平網(wǎng)頁(yè)制作以及安平網(wǎng)絡(luò)營(yíng)銷策劃等。多年來(lái),我們專注于互聯(lián)網(wǎng)行業(yè),利用自身積累的技術(shù)優(yōu)勢(shì)、行業(yè)經(jīng)驗(yàn)、深度合作伙伴關(guān)系等,向廣大中小型企業(yè)、政府機(jī)構(gòu)等提供互聯(lián)網(wǎng)行業(yè)的解決方案,安平網(wǎng)站推廣取得了明顯的社會(huì)效益與經(jīng)濟(jì)效益。目前,我們服務(wù)的客戶以成都為中心已經(jīng)輻射到安平省份的部分城市,未來(lái)相信會(huì)繼續(xù)擴(kuò)大服務(wù)區(qū)域并繼續(xù)獲得客戶的支持與信任!模板特化

有了前兩篇的基礎(chǔ),相信大家對(duì)模板編程已經(jīng)有一點(diǎn)初步的感覺(jué)了。趁熱打鐵,這一篇我們主要來(lái)介紹一下模板特化。

首先來(lái)看一下下面的例子:

templatevoid add(T &t1, const T &t2) {t1 += t2;
}

上面是一個(gè)簡(jiǎn)單的模板函數(shù),用于把第二個(gè)參數(shù)的值加到第一個(gè)參數(shù)中去。這個(gè)模板函數(shù)對(duì)于基本數(shù)據(jù)類型的實(shí)例化都是沒(méi)什么問(wèn)題的,但是如果是字符串的話,那將會(huì)有問(wèn)題:

void Demo() {int a = 1, b = 3;
  add(a, b); // add,調(diào)用符合預(yù)期
  
  char c1[16] = "abc";
  char c2[] = "123";

  add(c1, c2); // add, 調(diào)用不符合預(yù)期
}

這里的問(wèn)題就在于,對(duì)于字符串類型(這里指原始C字符串,而不是std::string)來(lái)說(shuō),「相加」并不是簡(jiǎn)單的+=,因?yàn)樽址饕怯米址羔榿?lái)承載的,指針相加是不合預(yù)期的。我們希望的是字符串拼接。

因此,我們希望,單獨(dú)針對(duì)于char *的實(shí)例化能夠擁有不同的行為,而不遵從「通用模板」中的定義。這種語(yǔ)法支持就叫做「特化」,或「特例」。可以理解為,針對(duì)于模板參數(shù)是某種特殊情況下進(jìn)行的特殊實(shí)現(xiàn)。

因此,我們?cè)谕ㄓ媚0宓亩x基礎(chǔ)上,再針對(duì)char *類型定義一個(gè)特化:

#includetemplatevoid add(T &t1, const T &t2) {t1 += t2;
}

template<>// 模板特化也要用模板前綴,但由于已經(jīng)特化了,所以參數(shù)為空
void add(char *&t1, char *const &t2) {// 特化要指定模板參數(shù),模板體中也要使用具體的類型
  std::strcat(t1, t2);
}

void Demo() {int a = 1, b = 3;
  add(a, b); // add是通過(guò)通用模板生成的,因此本質(zhì)是a += b,符合預(yù)期

  char c1[16] = "abc";
  char c2[] = "123";

  add(c1, c2); // add有定義特化,所以直接調(diào)用特化函數(shù),因此本質(zhì)是strcat(c1, c2),符合預(yù)期
}

上例簡(jiǎn)單展示了一下模板特化目標(biāo)解決的問(wèn)題,和其基本的語(yǔ)法。但其實(shí)模板特化遠(yuǎn)不止如此,它有著巨大的潛力。

模板的特化分兩種情況,一種是全特化(有的地方也叫特例),一種是偏特化(有的地方也叫部分特化)。全特化相對(duì)簡(jiǎn)單一些,筆者會(huì)先來(lái)介紹。而偏特化會(huì)伴隨SFINAE理論,它將會(huì)成為模板元編程最核心的理論基礎(chǔ)。

全特化與模板的鏈接方式

首先復(fù)習(xí)一下我們?cè)陂_(kāi)篇時(shí)候所提到的一個(gè)非常重要的概念。**模板本身不是可使用的代碼,而是一種代碼的升成方法。需要經(jīng)過(guò)實(shí)例化后才能成為實(shí)際可用的代碼。**比如說(shuō)模板函數(shù)需要指定模板參數(shù)(可以是顯式指定,也可以是編譯器自動(dòng)推導(dǎo))實(shí)例化后,才能成為函數(shù),同理,模板類也需要實(shí)例化后才能成為類。

然而「全特化」就是說(shuō),當(dāng)所有模板參數(shù)都指定了的時(shí)候,才叫「全」。那么上一節(jié)中add的示例就是一個(gè)全特化,因?yàn)樗局挥幸粋€(gè)模板參數(shù),把它特化了自然是「完全」特化的。

而要談到全特化,就不得不談到一個(gè)非常容易踩坑的點(diǎn),那就是模板的鏈接方式。在一個(gè)單獨(dú)的.cpp文件中使用模板并不會(huì)有什么鏈接性問(wèn)題,但如果在多個(gè)文件中都要使用呢?自然要通過(guò)「頭文件聲明+鏈接」的方式來(lái)完成了。

但模板本身又很特殊,它本身不是可用的代碼,而是代碼生成器,因此編譯器會(huì)在編譯期用模板來(lái)生成代碼,注意,這個(gè)時(shí)候還沒(méi)有開(kāi)始鏈接!所以問(wèn)題就產(chǎn)生了,假如我們按照直覺(jué)和習(xí)慣,把模板的聲明和定義分文件來(lái)寫(xiě),會(huì)怎么樣呢?請(qǐng)看下面示例:

tmp.h

#pragma once

templatevoid f(const T &t); // 聲明

tmp.cpp

#include "tmp.h"

templatevoid f(const T &t) {} // 實(shí)現(xiàn)

main.cpp

#include "tmp.h"

int main() {f(1); // ff(1.0); // freturn 0;
}

如果我們真的這么做了,你會(huì)發(fā)現(xiàn)鏈接時(shí)會(huì)報(bào)錯(cuò)。原因是這樣的,我們?cè)趖mp.h中的這種寫(xiě)法,并不是「聲明了一個(gè)模板函數(shù)」,模板函數(shù)本不是函數(shù),是不需要聲明的,大家記住模板永遠(yuǎn)是生成代碼的工具。所以tmp.h中的寫(xiě)法是「聲明了一組函數(shù)」,包括我們?cè)?code>main函數(shù)中使用的ff,之所以能通過(guò)編譯,就是因?yàn)閠mp.h中存在它們的聲明。換句話說(shuō),templatevoid f(const T &);相當(dāng)于void f(const int &);,void f(const double &);,void f(char *const &);……這一系列的「函數(shù)聲明」。

所以,編譯是沒(méi)問(wèn)題的,但是鏈接的時(shí)候會(huì)報(bào)找不到ff的實(shí)現(xiàn)。這是為什么呢?明明我在tmp.cpp中實(shí)現(xiàn)了呀!那我們來(lái)「換位思考一下」,假如你是編譯器,我們知道「編譯」過(guò)程是單文件行為,那么你現(xiàn)在來(lái)編譯main.cpp,首先進(jìn)行預(yù)處理,把#include替換成對(duì)應(yīng)頭文件內(nèi)容,那么main.cpp就變成了:

templatevoid f(const T &t);

int main() {f(1); // ff(1.0); // freturn 0;
}

上面的f是函數(shù)聲明,下面編譯主函數(shù)的時(shí)候,根據(jù)參數(shù)推導(dǎo)出了ff,于是,通過(guò)上面的「模板函數(shù)聲明」生成了2條實(shí)際的「函數(shù)聲明」語(yǔ)句,也就是:

void f(const int &);
void f(const double &);

調(diào)用都是符合聲明的,OK,結(jié)束編譯,我們得到了main.o。

好了,下面我們來(lái)編譯tmp.cpp。同理,先做預(yù)處理,得到了:

templatevoid f(const T &t);

templatevoid f(const T &t) {}

這時(shí),問(wèn)題的關(guān)鍵點(diǎn)來(lái)了!,這個(gè)模板函數(shù)f在當(dāng)前這個(gè)編譯單元中,并沒(méi)有任何實(shí)例化,那么你自然就不知道應(yīng)當(dāng)按這個(gè)模板來(lái)生成哪些實(shí)例。所以,你只能什么都不做,很無(wú)奈地生成了一個(gè)空白的tmp.o。

最后,main.o和tmp.o鏈接,main.o中的ff都找不到實(shí)現(xiàn),所以鏈接報(bào)錯(cuò)。

這就是模板的鏈接方式問(wèn)題,由于模板都是編譯期進(jìn)行實(shí)例化,因此,必須在編譯期就得知道需要哪些實(shí)例化,然后把這些實(shí)例化后的代碼編譯出來(lái),再去參與鏈接,才能保證結(jié)果正確。

所以,要保證編譯期能知道所有需要的實(shí)例,我們只能把模板實(shí)現(xiàn)放在頭文件里。這樣,每一個(gè)編譯單元都能根據(jù)自己需要的實(shí)例來(lái)生成代碼。也就是說(shuō),上面的代碼應(yīng)該改造成:

tmp.h

#pragma once
templatevoid f(const T &t);

templatevoid f(const T &t) {} // 當(dāng)然,文件內(nèi)部沒(méi)有聲明依賴關(guān)系的時(shí)候,聲明和實(shí)現(xiàn)可以合并

main.cpp

#include "tmp.h"

int main() {f(1);
  f(1.0);

  return 0;
}

這時(shí),在編譯main.cpp時(shí),就會(huì)把ff的實(shí)例都編譯出來(lái),這樣就不會(huì)鏈接報(bào)錯(cuò)了。

但這樣會(huì)引入另一個(gè)問(wèn)題,如果多個(gè).cpp引入同一個(gè)含有模板的.h文件,并做了相同的實(shí)例化,會(huì)不會(huì)生成多份函數(shù)實(shí)現(xiàn)呢?這樣鏈接的時(shí)候不是也會(huì)報(bào)錯(cuò)嗎?

設(shè)計(jì)編譯器的大佬們自然也想到這個(gè)問(wèn)題了,那么解決方法就是,通過(guò)模板實(shí)例出的內(nèi)容,會(huì)打上一個(gè)全局標(biāo)記,最終鏈接時(shí)只使用一份(畢竟是從同一份模板生成出來(lái)的,每一份自然是相同的)。再換句更通俗易懂的說(shuō)法就是模板實(shí)例一定是inline的,編譯器會(huì)給每個(gè)模板實(shí)現(xiàn)自動(dòng)打上inline標(biāo)記,確保鏈接時(shí)全局唯一。

現(xiàn)在我們?cè)倩仡^看一下全特化模板,全特化模板已經(jīng)是實(shí)例化過(guò)的了,因此并不會(huì)出現(xiàn)編譯期不知道要怎么實(shí)例化的問(wèn)題。如果這時(shí)我們還把實(shí)現(xiàn)放在頭文件中會(huì)怎么樣?

tmp.h

#pragma once
templatevoid f(T t) {} // 通用模板

template<>void f(int t) {} // 針對(duì)int的全特化

t1.cpp

#include "tmp.h"

void Demo1() {f(1); // f}

t2.cpp

#include "tmp.h"

void Demo2() {f(1); // f}

我們?cè)賮?lái)當(dāng)一次編譯期。首先編譯t1.cpp,預(yù)處理展開(kāi),得到了f的實(shí)現(xiàn),所以把f編譯過(guò)來(lái),輸出t1.o。同理,編譯t2.cpp后,也會(huì)有一份f的實(shí)現(xiàn)在t2.o中。最后鏈接的時(shí)候,發(fā)現(xiàn)f重定義了!

因此我們發(fā)現(xiàn),全特化的模板其實(shí)已經(jīng)不是模板了,在這里f會(huì)按照普通函數(shù)一樣來(lái)進(jìn)行編譯和鏈接。所以直接把實(shí)現(xiàn)放在頭文件中,就有可能在鏈接時(shí)重定義。解決方法有兩種,第一種就是我們手動(dòng)補(bǔ)上inline關(guān)鍵字,提示編譯期要打標(biāo)全局唯一。

tmp.h

#pragma once
templatevoid f(T t) {} // 通用模板,編譯器用通用模板生成的實(shí)例會(huì)自動(dòng)打上inline

template<>inline void f(int t) {} // 針對(duì)int的全特化,必須手動(dòng)用inline修飾后才能在編譯期打標(biāo)保證鏈接全局唯一

第二種方法就是,當(dāng)做普通函數(shù)處理,我們把實(shí)現(xiàn)單獨(dú)抽到一個(gè)編譯單元中獨(dú)立編譯,最后在鏈接時(shí)才能保證唯一:

tmp.h

#pragma once
templatevoid f(T t) {} // 通用模板

template<>void f(int t); // 針對(duì)int的全特化聲明(函數(shù)聲明)

tmp.cpp

#include "tmp.h"

template<>void f(int t) {} // 函數(shù)實(shí)現(xiàn)

之后,f會(huì)隨著tmp.cpp的編譯,單獨(dú)存在在tmp.o中,最后鏈接時(shí)就是唯一的了。

另外,對(duì)于特化的模板函數(shù)來(lái)說(shuō),參數(shù)必須是按照通用模板的定義來(lái)寫(xiě)的(包括個(gè)數(shù)、類型和順序),但對(duì)于模板類來(lái)說(shuō),則沒(méi)有任何要求,我們可以寫(xiě)一個(gè)跟通用模板壓根沒(méi)什么關(guān)系的一種特化,比如說(shuō):

templatestruct Test {// 通用模板中有2個(gè)成員變量,1個(gè)成員函數(shù)
  T a, b;
  void f();
};

template<>struct Test{// 特化的內(nèi)部定義可以跟通用模板完全不同
  double m;
  static int ff();
}
偏特化

偏特化又叫部分特化,既然是「部分」的,那么就不會(huì)像全特化那樣直接實(shí)例化了。偏特化的模板本質(zhì)上還是模板,它仍然需要編譯期來(lái)根據(jù)需要進(jìn)行實(shí)例化的,所以,在鏈接方式上來(lái)說(shuō),全特化要按普通函數(shù)/類/變量來(lái)處理,而偏特化模板要按模板來(lái)處理。

先明確一個(gè)點(diǎn):模板函數(shù)不支持偏特化,因此偏特化討論的主要是模板類。

我們先來(lái)看一個(gè)最簡(jiǎn)單的偏特化的例子:

templatestruct Test {};

templatestruct Test{};

上面代碼就是針對(duì)Test模板類,第一個(gè)參數(shù)為int時(shí)的「偏特化」,那么只要是第一個(gè)參數(shù)為int的時(shí)候,就會(huì)按照偏特化模板來(lái)進(jìn)行實(shí)例化,否則會(huì)按照通用模板進(jìn)行實(shí)例化。為了方便說(shuō)明,我們?cè)谕ㄓ媚0搴推鼗0逯屑右恍┯糜隍?yàn)證性的代碼:

#includetemplatestruct Test {};

templatestruct Test{static void f();
};

templatevoid Test::f() {std::cout<< "part specialization"<< std::endl;
}

void Demo() {Test::f(); // 按照偏特化實(shí)例化,有f函數(shù)
  Test::f(); // 按照偏特化實(shí)例化,有f函數(shù)
  Test::f(); // 按照通用模板實(shí)例化,不存在f函數(shù),編譯報(bào)錯(cuò)
}

偏特化模板本身仍然是模板,仍然需要經(jīng)歷實(shí)例化。但偏特化模板可以指定當(dāng)一些參數(shù)滿足條件時(shí),應(yīng)當(dāng)按照指定方式進(jìn)行實(shí)例化而不是通用模板定義的方式來(lái)實(shí)例化。

那如果偏特化和全特化同時(shí)存在呢?比如下面的情況:

templatestruct Test {}; // 【0】通用模板

templatestruct Test{}; // 【1】偏特化模板

template<>struct Test{}; // 【2】全特化模板

void Demo() {Testt; // 按照哪個(gè)實(shí)例化?
}

先說(shuō)答案,上面的實(shí)例會(huì)按照【2】的方式,也就是直接調(diào)用全特化。大致上來(lái)說(shuō),全特化優(yōu)先級(jí)高于偏特化,偏特化高于通用模板。

對(duì)于函數(shù)來(lái)說(shuō),模板函數(shù)不支持偏特化,但支持重載,并且重載的優(yōu)先級(jí)高于全特化。比如說(shuō):

void f(int a, int b) {} // 重載函數(shù)

templatevoid f(T1 a, T2 b) {} // 通用模板

template<>void f(int a, int b) {} // 全特化

void Demo() {f(1, 2); // 會(huì)調(diào)用重載函數(shù)
  f<>(1, 2); // 會(huì)調(diào)用全特化函數(shù)ff(2.5, 2.6); // 會(huì)用通用模板生成f}

回到模板類的偏特化上,除了上面那種制定某些參數(shù)的偏特化以外,還有一種相對(duì)復(fù)雜的偏特化,請(qǐng)看示例:

templatestruct Tool {}; // 這是另一個(gè)普通的模板類

templatestruct Test {}; // 【0】通用模板

templatestruct Test>{}; // 【1】偏特化

void Demo() {Testt1; // 使用【0】實(shí)例化TestTest>; // 使用【1】實(shí)例化Test>Test>; // 使用【1】實(shí)例化Test>}

有的資料會(huì)管上面這種特化叫做「模式特化」,用于區(qū)分普通的「部分特化」。但它們其實(shí)都屬于偏特化的一種,因?yàn)槠鼗际窍喈?dāng)于特化了參數(shù)的范圍。在上面的例子中,我們是針對(duì)于「參數(shù)是Tool的實(shí)例類型」這種情況進(jìn)行了特化。

所以,偏特化并不一定意味著模板參數(shù)數(shù)量變小,它有可能不變,甚至有可能是增加的,比如說(shuō):

templatestruct Tool {}; // 這是另一個(gè)普通的模板類

templatestruct Test {}; // 【0】通用模板

templatestruct Test>{}; // 【1】偏特化模板

templatestruct Test>{}; // 【2】偏特化模板

void Demo() {Testt1; // 【0】
  Test>t2; // 【2】
  Test>t3; // 【1】
}

所以偏特化的引入,讓模板編程這件事有了爆炸性的顛覆,因?yàn)槠渲械慕M合可以隨意發(fā)揮想象。但這里就引入了另一個(gè)問(wèn)題,就比如上例中,【1】和【2】都是偏特化的一種,但為什么Test>選擇了【2】而不是【1】呢?這么說(shuō),看來(lái)不僅僅是跟全特化和通用模板存在優(yōu)先級(jí)問(wèn)題,多種偏特化之間也仍然存在優(yōu)先級(jí)問(wèn)題,那么編譯器究竟是按照什么方式來(lái)進(jìn)行偏特化匹配的呢?這就是我們下一篇要著重研究的問(wèn)題了。

小結(jié)

這一篇我們主要介紹了模板的鏈接方式和模板的特化,重點(diǎn)希望讀者理解和掌握的是模板類的偏特化,因?yàn)镃++的模板元編程其實(shí)就是一系列模板的偏特化來(lái)實(shí)現(xiàn)各種靜態(tài)功能的。

下一篇會(huì)介紹偏特化的優(yōu)先級(jí)匹配法則和SFINAE特性。
C++模板元編程詳細(xì)教程(之四)

你是否還在尋找穩(wěn)定的海外服務(wù)器提供商?創(chuàng)新互聯(lián)www.cdcxhl.cn海外機(jī)房具備T級(jí)流量清洗系統(tǒng)配攻擊溯源,準(zhǔn)確流量調(diào)度確保服務(wù)器高可用性,企業(yè)級(jí)服務(wù)器適合批量采購(gòu),新人活動(dòng)首月15元起,快前往官網(wǎng)查看詳情吧

分享名稱:C++模板元編程詳細(xì)教程(之三)-創(chuàng)新互聯(lián)
文章起源:http://www.rwnh.cn/article46/dciieg.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供品牌網(wǎng)站制作、微信小程序、Google、手機(jī)網(wǎng)站建設(shè)網(wǎng)頁(yè)設(shè)計(jì)公司、動(dòng)態(tài)網(wǎng)站

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)

成都網(wǎng)站建設(shè)公司
陵水| 汶上县| 沈阳市| 中方县| 阆中市| 昌江| 淮南市| 江孜县| 涪陵区| 岐山县| 内黄县| 和林格尔县| 鄂尔多斯市| 隆尧县| 刚察县| 淄博市| 新邵县| 吕梁市| 胶州市| 临朐县| 湖南省| 桃源县| 谢通门县| 铜川市| 乐山市| 汕头市| 西峡县| 峨边| 鹰潭市| 安仁县| 泸溪县| 嘉定区| 新化县| 陆河县| 清河县| 吴堡县| 田阳县| 平塘县| 濮阳市| 永仁县| 潜江市|