C++11可變參數(shù)模版實(shí)例詳解
前言
注:這個(gè)是博主復(fù)習(xí)使用的專題,僅適用于自己以及學(xué)習(xí)過C++知識(shí)點(diǎn)的同學(xué)。
一、可變模版參數(shù)
1.1. 可變參數(shù)模版的概念
可變參數(shù)模版是 C++11 新增的特性之一,它對(duì)參數(shù)高度泛化能夠讓我們創(chuàng)建可以接受可變參數(shù)的函數(shù)模版和類模版。
- 在C++11之前,類模版和函數(shù)模版中只能包含固定數(shù)量的模版參數(shù),由于可變參數(shù)模版比較抽象,使用起來(lái)需要一定的技巧。
- 在C++之前其也有可變參數(shù)的概念,比如 printf 函數(shù)就可以接收任意多個(gè)參數(shù),但這是函數(shù)參數(shù)的可變參數(shù),并不是模版的可變參數(shù)。
注:本篇博客只講解函數(shù)模板的可變參數(shù)。
1.2. 可變參數(shù)模版的定義方法
函數(shù)的可變參數(shù)模板定義方式如下:
template<class...Args>
返回類型 函數(shù)名(Args...args)
{
// 函數(shù)體
}例如:
template<class... Args>
void ShowList(Args... args)
{}注:用省略號(hào)來(lái)指出一個(gè)模版參數(shù)或函數(shù)參數(shù)的表示一個(gè)包,在模版參數(shù)列表中,class... 或typename... 指出接下來(lái)的參數(shù)表示0或多個(gè)類型列表;在函數(shù)參數(shù)列表中,類型名后面跟... 指出接下來(lái)表示0或者多個(gè)形參對(duì)象列表;函數(shù)參數(shù)包可以用左值引用或右值引用表示。每個(gè)參數(shù)實(shí)例化都遵循引用折疊規(guī)則。
現(xiàn)在調(diào)用ShowList函數(shù)時(shí)就可以傳入任意多個(gè)參數(shù)了,并且這些參數(shù)可以是不同類型的。比如:
int main()
{
ShowList();
ShowList(1);
ShowList(1,'A');
ShowList(1,'A',string("hello"));
return 0;
}我們可以在函數(shù)模板中通過sizeof計(jì)算參數(shù)包中參數(shù)的個(gè)數(shù)。比如:
template<class... Args>
void ShowList(Args... args)
{
cout << sizeof...(args) << endl;
}
int main()
{
ShowList(); // 0
ShowList(1); // 1
ShowList(1,'A'); // 2
ShowList(1,'A',string("hello")); // 3
return 0;
}但是我們無(wú)法直接獲取參數(shù)包中的每個(gè)參數(shù),只能通過 展開參數(shù)包的方式 來(lái)獲取,這是使用可變參數(shù)模版的一個(gè)主要特點(diǎn),也是最大的難點(diǎn)。
他的本質(zhì)有兩種:
1. 結(jié)合引用折疊規(guī)則實(shí)例化出下面的函數(shù)。
void ShowList(); void ShowList(int arg1); void ShowList(int arg1,char arg2); void ShowList(int arg1,char arg2,string arg3);
2. 更本質(zhì)的是生成下面的模版參數(shù)。
void ShowList(); template<class T1> void ShowList(T1 arg1); template<class T1,class T2> void ShowList(T1 arg1,T2 arg2); template<class T1, class T2,class T3> void ShowList(T1 arg1, T2 arg2,T3 arg3);
注:語(yǔ)法并不支持使用 args[i] 的方式來(lái)獲取參數(shù)包中的參數(shù):
template<class... Args>
void ShowList(Args... args)
{
for (int i = 0; i < sizeof...(args); i++)
{
cout << args[i] << " ";
}
cout << endl;
}這個(gè)是錯(cuò)的。
因此要獲取參數(shù)包中的各個(gè)參數(shù),只能通過展開參數(shù)包的方式來(lái)獲取,一般我們會(huì)通過遞歸或逗號(hào)表達(dá)式來(lái)展開參數(shù)包。
1.3. 參數(shù)包的展開方式
1.3.1. 遞歸展開參數(shù)包
遞歸展開參數(shù)包的方式如下:
- 給函數(shù)模版增加一個(gè)模版參數(shù),這樣就可以從接收到的參數(shù)包中分離出一個(gè)參數(shù)出來(lái)。
- 在函數(shù)模版中遞歸調(diào)用該函數(shù)模版,調(diào)用時(shí)傳入剩下的參數(shù)包。
- 如此遞歸下去,每次分離出參數(shù)包中的一個(gè)參數(shù),直到參數(shù)包中的所有參數(shù)都被取出來(lái)。
例如打印調(diào)用函數(shù)時(shí)傳入的各個(gè)參數(shù),那么函數(shù)模版可以這樣子編寫:
//展開函數(shù)
template<class T, class ...Args>
void ShowList(T value, Args... args)
{
cout << value << " "; //打印分離出的第一個(gè)參數(shù)
ShowList(args...); //遞歸調(diào)用,將參數(shù)包繼續(xù)向下傳
}
這時(shí)我們面臨的問題就是,如何終止函數(shù)的遞歸調(diào)用。
編寫無(wú)參的遞歸終止函數(shù)
我們可以在剛才的基礎(chǔ)上,再編寫一個(gè)無(wú)參的遞歸終止函數(shù),該函數(shù)的函數(shù)名與展開函數(shù)的函數(shù)名相同。如下:
//遞歸終止函數(shù)
void ShowList()
{
cout << endl;
}
//展開函數(shù)
template<class T, class ...Args>
void ShowList(T value, Args... args)
{
cout << value << " "; //打印分離出的第一個(gè)參數(shù)
ShowList(args...); //遞歸調(diào)用,將參數(shù)包繼續(xù)向下傳
}
int main()
{
ShowList();
ShowList(1);
ShowList(1, 'A');
ShowList(1, 'A', string("hello"));
return 0;
}這樣一來(lái),當(dāng)遞歸調(diào)用 ShowList 函數(shù)模版時(shí),如果傳入的參數(shù)包中的參數(shù)個(gè)數(shù)為0,就會(huì)直接匹配到這個(gè)無(wú)參的遞歸終止函數(shù),就是遞歸的結(jié)束函數(shù)。
當(dāng)然也可以按照下面的寫:
void ShowList()
{
cout << endl;
}
template <class T, class ...Args>
void ShowList(T x, Args... args)
{
cout << x << " ";
ShowList(args...);
}
// 編譯時(shí)遞歸推導(dǎo)解析參數(shù)
template <class ...Args>
void Print(Args... args)
{
ShowList(args...);
}
int main()
{
Print(1);
Print(1, 'A');
Print(1, 'A', string("hello"));
return 0;
}1.3.2. 逗號(hào)表達(dá)式展開參數(shù)包
1. 通過列表獲取參數(shù)包中的參數(shù)
如果參數(shù)包中各個(gè)參數(shù)的類型都是整型,那么也可以把這個(gè)參數(shù)包放到列表當(dāng)中初始化這個(gè)整型數(shù)組,此時(shí)參數(shù)包中參數(shù)就放到數(shù)組中了。比如:
template<class...Args>
void Print(Args...args)
{
int arr[] = { args...};
for (auto e : arr)
{
cout << e << " ";
}
cout << endl;
}
int main()
{
Print(1);
Print(1,2);
Print(1,2,3);
return 0;
}但C++并不像Python這樣的語(yǔ)言,C++規(guī)定一個(gè)容器中存儲(chǔ)的數(shù)據(jù)類型必須是相同的,因此如果這樣寫的話,那么調(diào)用ShowList函數(shù)時(shí)傳入的參數(shù)只能是整型的,并且還不能傳入0個(gè)參數(shù),因?yàn)閿?shù)組的大小不能為0,因此我們還需要在此基礎(chǔ)上借助逗號(hào)表達(dá)式來(lái)展開參數(shù)包。
2. 通過逗號(hào)表達(dá)式展開參數(shù)包
雖然我們不能用不同類型的參數(shù)去初始化一個(gè)整型數(shù)組,但我們可以借助逗號(hào)表達(dá)式。
- 逗號(hào)表達(dá)式會(huì)從左到右依次計(jì)算各個(gè)表達(dá)式,并且將最后一個(gè)表達(dá)式的值作為返回值進(jìn)行返回。
- 將逗號(hào)表達(dá)式的最后一個(gè)表達(dá)式設(shè)置為一個(gè)整型值,確保逗號(hào)表達(dá)式返回的是一個(gè)整型值。
- 將處理參數(shù)包中參數(shù)的動(dòng)作封裝成一個(gè)函數(shù),將該函數(shù)的調(diào)用作為逗號(hào)表達(dá)式的第一個(gè)表達(dá)式。
這樣一來(lái),在執(zhí)行逗號(hào)表達(dá)式時(shí)就會(huì)先調(diào)用處理函數(shù)處理對(duì)應(yīng)的參數(shù),然后再將逗號(hào)表達(dá)式中的最后一個(gè)整型值作為返回值來(lái)初始化整型數(shù)組。比如:
template<class T>
const T& GetArg(const T& x)
{
cout << x << " ";
return x;
}
template<class...Args>
void Arguments(Args... args)
{}
template<class... Args>
void Print(Args... args)
{
Arguments(GetArg(args)...);
}
int main()
{
Print(1, string("2345"), 1.1);
return 0;
}他的本質(zhì)其實(shí)是:
編譯器在編譯的是偶,實(shí)例化下面的函數(shù):
template<class T>
const T& GetArg(const T& x)
{
cout << x << " ";
return x;
}
template<class...Args>
void Arguments(Args... args)
{}
template<class... Args>
void Print(int x,string y,double z)
{
Arguments(GetArg(x),GetArg(y),GetArg(z));
}
int main()
{
Print(1, string("2345"), 1.1);
return 0;
}1.4. STL容器匯總的emplace相關(guān)接口函數(shù)
C++11標(biāo)準(zhǔn)給STL中的容器增加emplace版本的插入接口,比如list容器的push_front、push_back和insert函數(shù),都增加了對(duì)應(yīng)的emplace_front、emplace_back和emplace函數(shù)。如下:


注意: emplace系列接口的可變模板參數(shù)類型都帶有“&&”,這個(gè)表示的是萬(wàn)能引用,而不是右值引用。
emplace系列接口的使用方式
emplace系列接口的使用方式與容器原有的插入接口的使用方式類似,但又有一些不同之處。
以list容器的emplace_back和push_back為例:
- 調(diào)用push_back函數(shù)插入元素時(shí),可以傳入左值對(duì)象或者右值對(duì)象,也可以使用列表進(jìn)行初始化。
- 調(diào)用emplace_back函數(shù)插入元素時(shí),也可以傳入左值對(duì)象或者右值對(duì)象,但不可以使用列表進(jìn)行初始化。
- 除此之外,emplace系列接口最大的特點(diǎn)就是,插入元素時(shí)可以傳入用于構(gòu)造元素的參數(shù)包。
emplace系列接口的意義
由于emplace系列接口的可變模版參數(shù)類型都是萬(wàn)能引用,因此既可以接收左值對(duì)象,也可以接收右值對(duì)象,還可以接收參數(shù)包。
- 如果調(diào)用emplace系列接口時(shí)傳入的是左值對(duì)象,那么首先需要先在此之前調(diào)用構(gòu)造函數(shù)實(shí)例化出一個(gè)左值對(duì)象,最終在使用定位new表達(dá)式調(diào)用構(gòu)造函數(shù)對(duì)空間進(jìn)行初始化時(shí),會(huì)匹配到拷貝構(gòu)造函數(shù)。
- 如果調(diào)用emplace系列接口時(shí)傳入的是右值對(duì)象,那么就需要在此之前調(diào)用構(gòu)造函數(shù)實(shí)例化出一個(gè)右值對(duì)象,最終在使用定位new表達(dá)式調(diào)用構(gòu)造函數(shù)對(duì)空間進(jìn)行初始化時(shí),就會(huì)匹配到移動(dòng)構(gòu)造函數(shù)。
- 如果調(diào)用emplace系列接口時(shí)傳入的是參數(shù)包,那么就可以直接調(diào)用函數(shù)進(jìn)行插入,并且最終在使用定位new表達(dá)式調(diào)用構(gòu)造函數(shù)對(duì)空間進(jìn)行初始化,匹配到的是構(gòu)造函數(shù)。
總結(jié)一下:
- 傳入左值對(duì)象,需要調(diào)用構(gòu)造函數(shù) + 拷貝構(gòu)造函數(shù)。
- 傳入右值對(duì)象,需要調(diào)用構(gòu)造函數(shù) + 移動(dòng)構(gòu)造函數(shù)。
- 傳入?yún)?shù)包,只需要調(diào)用構(gòu)造函數(shù)。
當(dāng)然,這里的前提是容器中存儲(chǔ)的元素所對(duì)應(yīng)的類,是一個(gè)需要深拷貝的類,并且該類實(shí)現(xiàn)了移動(dòng)構(gòu)造函數(shù)。否則調(diào)用emplace系列接口時(shí),傳入左值對(duì)象和傳入右值對(duì)象的效果都是一樣的,都需要調(diào)用一次構(gòu)造函數(shù)和一次拷貝構(gòu)造函數(shù)。
實(shí)際emplace系列接口的一部分功能和原有各個(gè)容器插入接口是重疊的,因?yàn)槿萜髟械膒ush_back、push_front和insert函數(shù)也提供了右值引用版本的接口,如果調(diào)用這些接口時(shí)如果傳入的是右值對(duì)象,那么最終也是會(huì)調(diào)用對(duì)應(yīng)的移動(dòng)構(gòu)造函數(shù)進(jìn)行資源的移動(dòng)的。
emplace接口的意義:
- emplace系列接口最大的特點(diǎn)就是支持傳入?yún)?shù)包,用這些參數(shù)包直接構(gòu)造出對(duì)象,這樣就能減少一次拷貝,這就是為什么有人說(shuō)emplace系列接口更高效的原因。
- 但emplace系列接口并不是在所有場(chǎng)景下都比原有的插入接口高效,如果傳入的是左值對(duì)象或右值對(duì)象,那么emplace系列接口的效率其實(shí)和原有的插入接口的效率是一樣的。
- emplace系列接口真正高效的情況是傳入?yún)?shù)包的時(shí)候,直接通過參數(shù)包構(gòu)造出對(duì)象,避免了中途的一次拷貝。
驗(yàn)證
如果要驗(yàn)證我們上述對(duì)emplace系列接口的說(shuō)法,需要借助一個(gè)深拷貝的類,下面模擬實(shí)現(xiàn)了一個(gè)簡(jiǎn)化版的string類,類當(dāng)中只編寫了我們需要用到的成員函數(shù)。
namespace xxhh
{
class string
{
public:
//構(gòu)造函數(shù)
string(const char* str = "")
{
cout << "string(const char* str) -- 構(gòu)造函數(shù)" << endl;
_size = strlen(str); //初始時(shí),字符串大小設(shè)置為字符串長(zhǎng)度
_capacity = _size; //初始時(shí),字符串容量設(shè)置為字符串長(zhǎng)度
_str = new char[_capacity + 1]; //為存儲(chǔ)字符串開辟空間(多開一個(gè)用于存放'\0')
strcpy(_str, str); //將C字符串拷貝到已開好的空間
}
//交換兩個(gè)對(duì)象的數(shù)據(jù)
void swap(string& s)
{
//調(diào)用庫(kù)里的swap
::swap(_str, s._str); //交換兩個(gè)對(duì)象的C字符串
::swap(_size, s._size); //交換兩個(gè)對(duì)象的大小
::swap(_capacity, s._capacity); //交換兩個(gè)對(duì)象的容量
}
//拷貝構(gòu)造函數(shù)(現(xiàn)代寫法)
string(const string& s)
:_str(nullptr)
, _size(0)
, _capacity(0)
{
cout << "string(const string& s) -- 拷貝構(gòu)造" << endl;
string tmp(s._str); //調(diào)用構(gòu)造函數(shù),構(gòu)造出一個(gè)C字符串為s._str的對(duì)象
swap(tmp); //交換這兩個(gè)對(duì)象
}
//移動(dòng)構(gòu)造
string(string&& s)
:_str(nullptr)
, _size(0)
, _capacity(0)
{
cout << "string(string&& s) -- 移動(dòng)構(gòu)造" << endl;
swap(s);
}
//拷貝賦值函數(shù)(現(xiàn)代寫法)
string& operator=(const string& s)
{
cout << "string& operator=(const string& s) -- 深拷貝" << endl;
string tmp(s); //用s拷貝構(gòu)造出對(duì)象tmp
swap(tmp); //交換這兩個(gè)對(duì)象
return *this; //返回左值(支持連續(xù)賦值)
}
//移動(dòng)賦值
string& operator=(string&& s)
{
cout << "string& operator=(string&& s) -- 移動(dòng)賦值" << endl;
swap(s);
return *this;
}
//析構(gòu)函數(shù)
~string()
{
//delete[] _str; //釋放_(tái)str指向的空間
_str = nullptr; //及時(shí)置空,防止非法訪問
_size = 0; //大小置0
_capacity = 0; //容量置0
}
private:
char* _str;
size_t _size;
size_t _capacity;
};
}由于我們?cè)趕tring的構(gòu)造函數(shù)、拷貝構(gòu)造函數(shù)和移動(dòng)構(gòu)造函數(shù)當(dāng)中均打印了一條提示語(yǔ)句,因此我們可以通過控制臺(tái)輸出來(lái)判斷這些函數(shù)是否被調(diào)用。
下面我們用一個(gè)容器來(lái)存儲(chǔ)模擬實(shí)現(xiàn)的string,并以不同的傳參形式調(diào)用emplace系列函數(shù)。比如:
#include<list>
int main()
{
list<pair<int, xxhh::string>> mylist;
pair<int, xxhh::string> kv(1, "one");
mylist.emplace_back(kv); //傳左值
cout << endl;
mylist.emplace_back(pair<int, xxhh::string>(2, "two")); //傳右值
cout << endl;
mylist.emplace_back(3, "three"); //傳參數(shù)包
return 0;
}
到此這篇關(guān)于十二、C++11可變參數(shù)模版的文章就介紹到這了,更多相關(guān)C++可變參數(shù)模版內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- C++11 引用折疊、完美轉(zhuǎn)發(fā)、可變模板參數(shù)、emplace系列接口詳解
- C++11可變參數(shù)模板詳解
- C++11右值引用與完美轉(zhuǎn)發(fā)及可變參數(shù)模板詳解
- C++11可變參數(shù)模板的具體實(shí)現(xiàn)
- C++11中可變模板參數(shù)的實(shí)現(xiàn)
- C++11中的可變參數(shù)模板/lambda表達(dá)式
- C++11新特性之變長(zhǎng)參數(shù)模板詳解
- 一篇文章讓你徹底明白c++11增加的變參數(shù)模板
- C++11 模板參數(shù)的“右值引用”是轉(zhuǎn)發(fā)引用嗎
- 基于C++11的threadpool線程池(簡(jiǎn)潔且可以帶任意多的參數(shù))
相關(guān)文章
C語(yǔ)言實(shí)現(xiàn)24點(diǎn)問題詳解
24點(diǎn)問題就是在屏幕上輸入1?10范圍內(nèi)的4個(gè)整數(shù)(可以有重復(fù)),對(duì)它們進(jìn)行加、減、乘、除四則運(yùn)算后(可以任意的加括號(hào)限定計(jì)算的優(yōu)先級(jí)),尋找計(jì)算結(jié)果等于24的表達(dá)式。本文將通過C語(yǔ)言實(shí)現(xiàn)24點(diǎn)問題的求解,需要的可以參考一下2021-12-12
C++實(shí)現(xiàn)LeetCode(159.最多有兩個(gè)不同字符的最長(zhǎng)子串)
這篇文章主要介紹了C++實(shí)現(xiàn)LeetCode(159.最多有兩個(gè)不同字符的最長(zhǎng)子串),本篇文章通過簡(jiǎn)要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-07-07
C++中std::condition_variable 條件變量的使用
std::condition_variable是C++多線程編程中用于線程間同步的核心工具,適用于需要協(xié)調(diào)多個(gè)線程行為、避免資源浪費(fèi)的場(chǎng)景,下面就來(lái)介紹一下std::condition_variable 條件變量的使用,2025-07-07
C++實(shí)現(xiàn)并優(yōu)化異常系統(tǒng)
異常處理是C++的一項(xiàng)語(yǔ)言機(jī)制,用于在程序中處理異常事件,下面這篇文章主要給大家介紹了關(guān)于C++中異常的相關(guān)資料,文中通過實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-08-08
C語(yǔ)言實(shí)現(xiàn)數(shù)字連連看
這篇文章主要為大家詳細(xì)介紹了C語(yǔ)言實(shí)現(xiàn)數(shù)字連連看游戲,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-09-09

