類模板參數(shù)的默認(rèn)值

在定義類模板的時(shí)候淑翼,可以給模板參數(shù)指定一個(gè)默認(rèn)值腐巢,

template <typename Tp, std::size_t kNum = 5>
class Foo
{
public:
  std::array<Tp, kNum> arr;
};

如上所示,第二個(gè)是非類型模板參數(shù) std::size_t窒舟,其默認(rèn)值為 5系忙。我們可以這樣使用 Foo

Foo<char, 20> foo_char20; // arr 為 std::array<char, 20>
Foo<char> foo_char5; // arr 為 std::array<char, 5>惠豺,由于默認(rèn)值的存在银还,可以不用指定第二個(gè)參數(shù)

還可以對(duì) Foo 進(jìn)行偏特化,

template <std::size_t kNum>
class Foo<int, kNum>
{
public:
  std::array<int, kNum> arr;
};

此時(shí)洁墙,對(duì)于偏特化 Foo 的第二個(gè)模板參數(shù) kNum蛹疯,其默認(rèn)值依然是 5,

Foo<int, 20> foo_int20; // 對(duì)偏特化版本進(jìn)行實(shí)例化,arr 為 std::array<int, 20>
Foo<int> foo_int5; // 對(duì)偏特化版本進(jìn)行實(shí)例化热监,arr 為 std::array<int, 5>

注意捺弦,在偏特化的 Foo 中,kNum 不可以再指定默認(rèn)值,只能從原型那里獲取默認(rèn)值列吼。


std::unique_ptr 模板參數(shù)的默認(rèn)值

template <typename _Tp>
struct default_delete
{
  void operator()(_Tp* ptr) const
  {
    delete ptr;
  }
};

template <typename _Tp, typename _Dp = std::default_delete<_Tp>>
class unique_ptr
{
public:
  typedef _Tp  element_type;
  typedef _Dp  deleter_type;
};

以上是 GCC 中 std::default_deletestd::unique_ptr 代碼的簡(jiǎn)化幽崩。其中 std::default_delete 是標(biāo)準(zhǔn)庫(kù)提供的用于刪除指針的類,大多數(shù)情況用在函數(shù)對(duì)象上寞钥。
可以看到 std::unique_ptr 的第二個(gè)模板參數(shù)的默認(rèn)值是實(shí)例化的 std::default_delete<_Tp>慌申,所以在使用的時(shí)候只需要提供第一個(gè)模板參數(shù)就可以,

std::unique_ptr<int> uniptr_int;

當(dāng)?shù)竭_(dá)了 uniptr_int 作用域結(jié)束的地方理郑,析構(gòu)函數(shù)會(huì)利用 _Dp 來(lái)刪除內(nèi)部實(shí)際的指針蹄溉。
同時(shí),標(biāo)準(zhǔn)庫(kù)也提供了數(shù)組版本的 std::default_deletestd::unique_ptr您炉,即提供了一個(gè)數(shù)組版本的偏特化柒爵,

template <typename _Tp>
struct default_delete<_Tp[]>
{
  void operator()(_Tp* ptr) const
  {
    // 對(duì)數(shù)組指針進(jìn)行釋放
    delete[] ptr;
  }
};

template <typename _Tp, typename _Dp>
class unique_ptr<_Tp[], _Dp>
{
public:
  typedef _Tp  element_type;
  typedef _Dp  deleter_type;
};

但是,如果仔細(xì)觀察兩個(gè)版本的 std::unique_ptr赚爵,會(huì)發(fā)現(xiàn)一個(gè)與常識(shí)矛盾的地方棉胀。
數(shù)組版本的第二個(gè)模板參數(shù)的默認(rèn)值與原型是相同的,所以可以這樣子看待數(shù)組版本的偏特化囱晴,

// 實(shí)際上膏蚓,這樣寫是無(wú)法通過(guò)編譯的,偏特化版本參數(shù)的默認(rèn)值必須從原型獲得畸写,即這個(gè)默認(rèn)值是不能夠?qū)懗鰜?lái)的
template <typename _Tp, typename _Dp = std::default_delete<_Tp>>
class unique_ptr<_Tp[], _Dp> { };

如果我們這樣子使用 std::unique_ptr<int[]> uniptr_intarr;驮瞧,那么 _Tpint,進(jìn)而 _Dp 就是 std::default_delete<int>枯芬;如果到達(dá)了 uniptr_intarr 作用域的盡頭论笔,析構(gòu)函數(shù)為釋放指針調(diào)用的是 delete ,而不是 delete[]千所,這樣會(huì)造成內(nèi)存泄漏狂魔。
可是,通過(guò)進(jìn)一步的測(cè)試淫痰,發(fā)現(xiàn)實(shí)際情況與上述的理解是不一樣的最楷,

std::cout << std::boolalpha
          << std::is_same<std::unique_ptr<int[]>::deleter_type, std::default_delete<int[]>>::value << '\n';

輸出是 true,也就是說(shuō)數(shù)組版本的 _Dpstd::default_delete<int[]>待错。
這是怎么一回事呢籽孙?
原因是我們看待模板方角度出了問(wèn)題,應(yīng)該把重點(diǎn)放在實(shí)例化模板的地方火俄。
對(duì)于 std::unique_ptr 模板原型和數(shù)組偏特化版本犯建,我們過(guò)于看重 template <typename _Tp, typename _Dp = std::default_delete<_Tp>>template <typename _Tp, _Dp> 這兩行,從而認(rèn)為數(shù)組偏特化版本的第二個(gè)模板參數(shù)默認(rèn)值就是直接從原型中拷貝過(guò)來(lái)的瓜客。實(shí)際上适瓦,這兩行是模板聲明的必要語(yǔ)法竿开,作用是告訴編譯器下面是個(gè)模板,還確定了需要推導(dǎo)的模板參數(shù)的個(gè)數(shù)玻熙;而編譯器用來(lái)推導(dǎo)模板參數(shù)的是 class unique_ptrclass unique_ptr<_Tp[], _Dp> 這兩行否彩,而第一個(gè)可以理解成 class unique_ptr<_Tp, _Dp> 的簡(jiǎn)略寫法(這種簡(jiǎn)略寫法將在下文證實(shí))。
關(guān)注點(diǎn)轉(zhuǎn)移之后就可以解釋默認(rèn)值的含義了揭芍。模板參數(shù)的默認(rèn)值是告訴編譯器胳搞,在推導(dǎo)參數(shù)的時(shí)候該采取怎樣的行為。std::unique_ptr 的第二個(gè)模板參數(shù)的默認(rèn)值 std::default_delete<_Tp> 的含義是称杨,將 class unique_ptr<_Tp, _Dp> 的第一個(gè)實(shí)參 _Tp 當(dāng)作 std::default_delete 的模板參數(shù)。對(duì)于 std::unique_ptr<int> uniptr_int; 第一個(gè)實(shí)參是 int筷转,所以第二個(gè)實(shí)參就是 std::default_delete<int>姑原;由于數(shù)組偏特化版本采用了原型的含義,對(duì)于 std::unique_ptr<int[]> uniptr_int呜舒,第一個(gè)實(shí)參是 int[]锭汛,請(qǐng)注意我們關(guān)注的是 class unique_ptr<_Tp[], _Dp> 而不是 template <typename _Tp, typename _Dp>,前者的第一個(gè)實(shí)參是 int[]袭蝗,而后者的第一個(gè)實(shí)參是 int唤殴,因此第二個(gè)實(shí)參是 std::default_delete<int[]>
可以再舉一個(gè)簡(jiǎn)單的例子來(lái)進(jìn)一步佐證到腥,

template <typename Tp1, typename Tp2 = Tp1>
class DefaultArgTest
{
public:
  using type = Tp2;

  DefaultArgTest() {
    std::cout << __PRETTY_FUNCTION__ << '\n';
  }
};

// 數(shù)組版本的偏特化
template <typename Tp1, typename Tp2>
class DefaultArgTest<Tp1[], Tp2>
{
public:
  using type = Tp2;

  DefaultArgTest() {
    std::cout << __PRETTY_FUNCTION__ << '\n';
  }
};

DefaultArgTest<int> dat_int;
DefaultArgTest<int[]> dat_intarr;

std::cout << std::boolalpha
          << std::is_same<decltype(dat_int)::type, int>::value << '\n'
          << std::is_same<decltype(dat_intarr)::type, int[]>::value << '\n';

__PRETTY_FUNCTION__ 是 GCC 編譯器添加到每個(gè)函數(shù)的靜態(tài)字符串常量(如果用到的話)朵逝,字符串的內(nèi)容是該函數(shù)的簽名。
上述代碼的輸出是,

DefaultArgTest<Tp1, Tp2>::DefaultArgTest() [with Tp1 = int; Tp2 = int]
DefaultArgTest<Tp1 [], Tp2>::DefaultArgTest() [with Tp1 = int; Tp2 = int []]
true
true

從第一條輸出可以證實(shí)上文中的簡(jiǎn)略寫法乡范,即 class DefaultArgTestclass DefaultArgTest<Tp1, Tp2> 的簡(jiǎn)略寫法配名。但是在代碼中我們只能寫成前者,因?yàn)槊總€(gè)偏特化版本都需要原型作為基礎(chǔ)晋辆,如果我們寫成后者渠脉,編譯器會(huì)認(rèn)為這是一個(gè)偏特化版本,因而就沒(méi)有了原型瓶佳,這就造成了沖突芋膘。


std::void_t

std::void_t 是 C++17 標(biāo)準(zhǔn)庫(kù)中提供的,定義于 <type_traits>霸饲,其原型如下为朋,

template <typename...>
using void_t = void;

std::void_t 的作用是,利用 SFINAE 來(lái)判定某些類型是否滿足我們的要求贴彼,

template <typename, typename = std::void_t<>>
struct HasTypeMember : std::false_type { };

template <typename Tp>
struct HasTypeMember<Tp, std::void_t<typename Tp::type>> : std::true_type { };

上述代碼中潜腻,有兩點(diǎn)需要注意的:之所以要利用默認(rèn)值,是為了在使用 HasTypeMember 的時(shí)候只提供一個(gè)實(shí)參即可 HasTypeMember<SomeType>::value器仗;如果偏特化版本沒(méi)有出錯(cuò)融涣,原型和偏特化版本的第二個(gè)參數(shù)都是 void童番,此時(shí)編譯器依然會(huì)選擇偏特化版本,因?yàn)槠鼗姹颈仍透泳_威鹿,詳見(jiàn)partial ordering剃斧。
如此一來(lái),就可以利用 HasTypeMember 來(lái)檢測(cè)一個(gè)類的內(nèi)部是否具有 type 這個(gè)類型忽你。而這種檢測(cè)一般都用在編譯時(shí)幼东,

class TypeMember
{
public:
  using type = int;
};

class NoTypeMember { };

template <typename Tp, typename = std::enable_if_t<HasTypeMember<Tp>::value>>
class TypeMemberTest
{
public:
  typename Tp::type data;
};

TypeMemberTest<TypeMember> has_type_member; // OK
TypeMemberTest<NoTypeMember> no_type_member; // ERROR, 編譯失敗

話題之外

上文探討了 std::unique_ptr 的數(shù)組偏特化版本,我們提供的實(shí)參是 int[]科雳,需要闡明的是 int*根蟹,int[]int[size] 是三種不同的類型,

std::cout << std::boolalpha
          << std::is_same<int*, int[]>::value << '\n'
          << std::is_same<int*, int[3]>::value << '\n'
          << std::is_same<int[], int[3]>::value << '\n';

輸出如下糟秘,

false
false
false

但是對(duì)于一個(gè)具體的數(shù)組 int arr[3];简逮,當(dāng)將其作為實(shí)參傳遞給函數(shù)時(shí)會(huì)自動(dòng)的退化成指針,所以在大多數(shù)情況下我們都會(huì)認(rèn)為 int[]尿赚,int[size]int* 是同一類型散庶,

void Func1(int arr[3])
{
  std::cout << std::is_same<decltype(arr), int*> << '\n';
}

void Func2(int arr[])
{
  std::cout << std::is_same<decltype(arr), int*> << '\n';
}

int arr[4]; // 注意數(shù)組維度是 4
std::cout << std::boolalpha;
Func1();
Func2();

輸出如下,

true
true

但是在模板中凌净,就不能按照經(jīng)驗(yàn)來(lái)了悲龟,這三個(gè)不同類型就會(huì)體現(xiàn)出其不同來(lái)。由于 std::is_same 也是模板须教,所以才能夠?qū)⑦@三種不同類型反映出來(lái)没卸。
因此迁筛,我們不能夠這樣使用 std::unique_ptr<int[3]> uniptr_intarr3;,因?yàn)?std::unique_ptr 沒(méi)有這種形式的偏特化版本。
函數(shù)與函數(shù)指針也不是同一類型这橙,可以通過(guò)類似的方法進(jìn)行證實(shí),這里就略過(guò)了。請(qǐng)參考 std::decay


參考

[1] partial template specialization
[2] std::void_t

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌隅茎,老刑警劉巖,帶你破解...
    沈念sama閱讀 210,978評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件魂毁,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡出嘹,警方通過(guò)查閱死者的電腦和手機(jī)席楚,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,954評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)税稼,“玉大人烦秩,你說(shuō)我怎么就攤上這事垮斯「艚冢” “怎么了衔统?”我有些...
    開(kāi)封第一講書(shū)人閱讀 156,623評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵盹憎,是天一觀的道長(zhǎng)袜刷。 經(jīng)常有香客問(wèn)我申窘,道長(zhǎng)嫁怀,這世上最難降的妖魔是什么牵触? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,324評(píng)論 1 282
  • 正文 為了忘掉前任草娜,我火速辦了婚禮墩剖,結(jié)果婚禮上猴凹,老公的妹妹穿的比我還像新娘。我一直安慰自己岭皂,他們只是感情好郊霎,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,390評(píng)論 5 384
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著爷绘,像睡著了一般书劝。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上土至,一...
    開(kāi)封第一講書(shū)人閱讀 49,741評(píng)論 1 289
  • 那天购对,我揣著相機(jī)與錄音,去河邊找鬼陶因。 笑死骡苞,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的楷扬。 我是一名探鬼主播解幽,決...
    沈念sama閱讀 38,892評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼烘苹!你這毒婦竟也來(lái)了躲株?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 37,655評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤螟加,失蹤者是張志新(化名)和其女友劉穎徘溢,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體捆探,經(jīng)...
    沈念sama閱讀 44,104評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡然爆,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,451評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了黍图。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片曾雕。...
    茶點(diǎn)故事閱讀 38,569評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖助被,靈堂內(nèi)的尸體忽然破棺而出剖张,到底是詐尸還是另有隱情切诀,我是刑警寧澤,帶...
    沈念sama閱讀 34,254評(píng)論 4 328
  • 正文 年R本政府宣布搔弄,位于F島的核電站幅虑,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏顾犹。R本人自食惡果不足惜倒庵,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,834評(píng)論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望炫刷。 院中可真熱鬧擎宝,春花似錦、人聲如沸浑玛。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,725評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)顾彰。三九已至极阅,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間涨享,已是汗流浹背涂屁。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,950評(píng)論 1 264
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留灰伟,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,260評(píng)論 2 360
  • 正文 我出身青樓儒旬,卻偏偏與公主長(zhǎng)得像栏账,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子栈源,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,446評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容

  • 接著上節(jié) condition_varible 挡爵,本節(jié)主要介紹future的內(nèi)容,練習(xí)代碼地址甚垦。本文參考http:/...
    jorion閱讀 14,776評(píng)論 1 5
  • C++運(yùn)算符重載-下篇 本章內(nèi)容:1. 運(yùn)算符重載的概述2. 重載算術(shù)運(yùn)算符3. 重載按位運(yùn)算符和二元邏輯運(yùn)算符4...
    Haley_2013閱讀 1,435評(píng)論 0 49
  • 1. 什么是智能指針茶鹃? 智能指針是行為類似于指針的類對(duì)象,但這種對(duì)象還有其他功能艰亮。 2. 為什么設(shè)計(jì)智能指針闭翩? 引...
    MinoyJet閱讀 636評(píng)論 0 1
  • 前言 把《C++ Primer》[https://book.douban.com/subject/25708312...
    尤汐Yogy閱讀 9,511評(píng)論 1 51
  • 這里把STL里處理iterator的tag-dispatching + trait class機(jī)制提取一點(diǎn)出來(lái)并淺...
    Quasars閱讀 519評(píng)論 0 1