在定義類模板的時(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_delete 和 std::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_delete 和 std::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;
驮瞧,那么 _Tp 是 int,進(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ù)組版本的 _Dp 是 std::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_ptr
和 class 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 DefaultArgTest
是 class 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。