最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

C++11 引用折疊、完美轉(zhuǎn)發(fā)、可變模板參數(shù)、emplace系列接口詳解

 更新時間:2026年06月13日 08:49:01   作者:Mortalbreeze  
本文詳細解析了C++中引用折疊、完美轉(zhuǎn)發(fā)、可變參數(shù)模板及emplace系列接口的概念與應用,涵蓋引用折疊規(guī)則、完美轉(zhuǎn)發(fā)原理、模板參數(shù)包與函數(shù)參數(shù)包的使用等核心概念,感興趣的朋友一起看看吧

一、引用折疊

1.1 引用折疊的產(chǎn)生原因

在C++中不能定義引用的引用,但可以通過 模板 或 typedef  中的類型操作可以構(gòu)成引用的引用。

示例1:不能定義引用的引用

int& &&r = i; // 編譯錯誤

示例2:通過 typedef 中的類型操作構(gòu)成引用的引用

typedef int& lref;
typedef int&& rref;
lref& r1 = n;  // r1 的類型是int& 
lref&& r2 = n; // r2 的類型是int& 
rref& r3 = n;  // r3 的類型是int& 
rref&& r4 = 1; // r4 的類型是int&&

示例3:通過 模板 中的類型操作構(gòu)成引用的引用

template<class T>
void f1(T& x)
{}
int main()
{
    int n = 10;
    f1<int&&>(n);
    f1<int&>(n);
    return 0;
}

1.2 什么是引用折疊

通過模板或typedef中的類型操作可以構(gòu)成引用的引用時,這時C++11將這種情況稱為引用折疊。

1.3 引用折疊的規(guī)則

右值引用的右值引用折疊成右值引用,所以其他組合均折疊成左值引用。

左值引用 + 左值引用 -> 左值引用

左值引用 + 右值引用 -> 左值引用

右值引用 + 左值引用 -> 左值引用

右值引用 + 右值引用 -> 右值引用

示例1:T& x

// 由于引?折疊規(guī)則,f1實例化以后總是?個左值引?
template<class T>
void f1(T& x)
{}
int main()
{
    int n = 0;
    // 沒有折疊 -> 實例化為 void f1(int& x)
    f1<int>(n);
    f1<int>(0); // 編譯錯誤,0是右值,采用左值引用需要const修飾
    // 折疊 -> 實例化為 void f1(int& x)
    f1<int&>(n);    
    f1<int&>(0); // 編譯錯誤
    // 折疊 -> 實例化為 void f1(int& x)
    f1<int&&>(n);
    f1<int&&>(0); // 編譯錯誤
    // 沒有折疊 -> 實例化為 void f1(const int& x)
    f1<const int>(n);
    f1<const int>(0);
    // 折疊 -> 實例化為 void f1(const int& x)
    f1<const int&>(n);
    f1<const int&>(0);
    // 折疊 -> 實例化為 void f1(const int& x)
    f1<const int&&>(n);
    f1<const int&&>(0);
    return 0;
}

示例2:T&& x

// 由于引用折疊限定,f2實例化后可以是左值引用,也可以是右值引用?
template<class T>
void f2(T&& x)
{}
int main()
{
    // 沒有折疊 -> 實例化為void f2(int&& x)?
    f2<int>(n); // 編譯錯誤,右值引用不能直接接受左值
    f2<int>(0);
    // 折疊 -> 實例化為 void f2(int& x)?
    f2<int&>(n);
    f2<int&>(0); // 編譯錯誤
    // 折疊 -> 實例化為 void f2(int&& x)?
    f2<int&&>(n); // 報錯?
    f2<int&&>(0);
    // 沒有折疊 -> 實例化為 void f2(const int&& x)?
    f2<const int>(n); // 編譯錯誤,右值引用不能直接接受左值
    f2<const int>(0);
    // 折疊 -> 實例化為 void f2(const int& x)?
    f2<const int&>(n);
    f2<const int&>(0); 
    // 折疊 -> 實例化為 void f2(const int&& x)?
    f2<const int&&>(n); // 編譯錯誤
    f2<const int&&>(0);
    return 0;
}

示例3:

template<class T>
void Function(T&& t)
{
    int a = 0;
    T x = a;
    x++;
    cout << &a << endl;
    cout << &x << endl << endl;
}
int main()
{
    // 0是右值 -> T為int -> 模板實例化為 void Function(int&& t)
    // Function中 x++ 不會改變a的值
    Function(0);
    int n = 10;
    // n是左值 -> T為int& -> 模板實例化為 void Function(int& t)
    // Function中 x++ 會改變a的值
    Function(n);
    const int m = 2;
    // m是左值 -> T為const int& -> 模板實例化為 void Function(const int& t)
    // Function中 x++ 會編譯錯誤
    Function(m);
    // std::move(m)是右值 -> T為const int -> 
    // 模板實例化為 void Function(const int&& t)
    // Function中 x++ 會編譯錯誤
    Function(std::move(m));
    return 0;
}

總結(jié):像f2和Function這樣的函數(shù)模板,T&&x 參數(shù)看起來是右值引用參數(shù),但由于引用折疊的規(guī)則,傳遞左值時就是左值引用,傳遞右值時就是右值引用,這種函數(shù)模板的參數(shù)被稱作萬能引用。

二、完美轉(zhuǎn)發(fā)

2.1 完美轉(zhuǎn)發(fā)的產(chǎn)生原因

在C++中,無論是左值引用或者右值引用,它們本身的屬性都是左值屬性(在右值引用中,我們知道一個右值被右值引用引用后,這個右值引用變量的屬性是左值)。也就是說,像上面Function中t的屬性始終是左值,那么我們把t轉(zhuǎn)遞給另外的函數(shù),那么匹配的都是左值引用的函數(shù),假如我們想要保持t對象的屬性,就需要借助完美轉(zhuǎn)發(fā)完成。

2.2 什么是完美轉(zhuǎn)發(fā)

// 重載1:接收左值引用參數(shù)(最常用)
template<typename _Tp>
constexpr _Tp&& forward(typename remove_reference<_Tp>::type& __t) noexcept
{
    return static_cast<_Tp&&>(__t);
}
// 重載2:接收右值引用參數(shù),禁止傳入左值
template<typename _Tp>
constexpr _Tp&& forward(typename remove_reference<_Tp>::type&& __t) noexcept
{
    static_assert(!is_lvalue_reference_v<_Tp>, "forward T& cannot bind rvalue");
    return static_cast<_Tp&&>(__t);
}

完美轉(zhuǎn)發(fā)就是把參數(shù)原封不動地傳遞給其他函數(shù),保持其左值、右值以及類型特性(如const)不變。

forward本質(zhì)是一個函數(shù)模板,它主要通過引用折疊的方式來實現(xiàn)。

示例:

未使用完美轉(zhuǎn)發(fā)

#include <iostream>
using namespace std;
void Fun(int& x) { cout << "左值引用" << endl; }
void Fun(const int& x) { cout << "const 左值引用" << endl; }
void Fun(int&& x) { cout << "右值引用" << endl; }
void Fun(const int&& x) { cout << "const 右值引用" << endl; }
template<class T>
void Function(T&& t)
{
    Fun(t);
}
int main()
{
    // 10是右值,推導出T為int,模板實例化為void Function(int&& t)?
    Function(10); // 右值?
    int a = 0;
    // a是左值,推導出T為int&,引用折疊,模板實例化為void Function(int& t)?
    Function(a); // 左值?
    // move(a)是右值,推導出T為int,模板實例化為void Function(int&& t)?
    Function(move(a)); // 右值?
    const int b = 8;
    // a是左值,推導出T為const int&,引用折疊,模板實例化為void Function(const int& t)
    Function(b); // const 左值?
    // move(b)右值,推導出T為const int,模板實例化為void Function(const int&& t)
    Function(move(b)); // const 右值?
    return 0;
}

代碼運行結(jié)果:

左值引用
左值引用
左值引用
const 左值引用
const 左值引用

使用完美轉(zhuǎn)發(fā)

#include <iostream>
using namespace std;
void Fun(int& x) { cout << "左值引用" << endl; }
void Fun(const int& x) { cout << "const 左值引用" << endl; }
void Fun(int&& x) { cout << "右值引用" << endl; }
void Fun(const int&& x) { cout << "const 右值引用" << endl; }
template<class T>
void Function(T&& t)
{
    //Fun(t);
    Fun(forward<T>(t));
}
int main()
{
    // 10是右值,推導出T為int,模板實例化為void Function(int&& t)?
    Function(10); // 右值?
    int a = 0;
    // a是左值,推導出T為int&,引用折疊,模板實例化為void Function(int& t)?
    Function(a); // 左值?
    // move(a)是右值,推導出T為int,模板實例化為void Function(int&& t)?
    Function(move(a)); // 右值?
    const int b = 8;
    // a是左值,推導出T為const int&,引用折疊,模板實例化為void Function(const int& t)
    Function(b); // const 左值?
    // move(b)右值,推導出T為const int,模板實例化為void Function(const int&& t)
    Function(move(b)); // const 右值?
    return 0;
}

代碼運行結(jié)果:

右值引用
左值引用
右值引用
const 左值引用
const 右值引用

三、可變參數(shù)模板

3.1 什么是可變參數(shù)模板

可變參數(shù)模板就是參數(shù)數(shù)量可變的函數(shù)模板和類模板,可變數(shù)目的參數(shù)被稱為參數(shù)包,參數(shù)包有兩種存在形式:模板參數(shù)包,表示零或多個模板參數(shù);函數(shù)參數(shù)包,表示零或多個函數(shù)參數(shù)。

3.2 基本語法

// 傳值傳參
template <class... Args>?
void Func(Args... args)
{
? ? ? ? // ...
}
// 左值引用傳參 ---> 遵守引用折疊的規(guī)則
template <class... Args>?
void Func(Args&... args)
{
? ? ? ? // ...
}
// 右值引用傳參 ---> 遵守引用折疊的規(guī)則
template <class... Args>?
void Func(Args&&... args)
{
? ? ? ? // ...
}
templagte <class ...Args> 這種寫法也是支持的

說明:我們用省略號來指出一個模板參數(shù)或函數(shù)參數(shù)表示一個包,這就指出template <class ...Args> 中的Args可以是任何合法的變量名,它會被實例化為類型名稱;在函數(shù)參數(shù)列表中,類型名稱后面跟...指出接下來表示零或多個參數(shù);函數(shù)參數(shù)包可以是傳值作為參數(shù),也可以用左值引用或右值引用,但每個參數(shù)實例化時需要遵守引用折疊規(guī)則

3.4 如何理解參數(shù)包

template<class ...Args>
void Func(Args... args) {}
這里實際上有兩個參數(shù)包
1. 模板參數(shù)包
對于template<class ...Args>, Args是模板參數(shù)包
Args 可以代表零個或多個類型
例如:
Func(10, 3.14, "hello");
編譯器推導:
Args = <int, double, const char*>
此時Args對應了一組類型 int double const char*,為模板參數(shù)包
2. 函數(shù)參數(shù)包
對于Args... args,args是函數(shù)參數(shù)包
args 可以代表零個或多個參數(shù)
例如:
Func(10, 3.14, "hello");
調(diào)用函數(shù)時,args = <10, 3.14, "hello">
此時args對于了一組實參,為函數(shù)參數(shù)包

對于上面的示例,很多人誤認為Args... 整體叫做參數(shù)包,其實不完全正確。

對于 template<class... Args> 含義是 class... 告訴編譯器后面的 Args 是一個模板參數(shù)包。

因此 Args 是模板參數(shù)包的名字。

對于 Args... args 含義是 Args... 告訴編譯器后面的 args 是一個函數(shù)參數(shù)包。

因此 args 是函數(shù)參數(shù)包的名字。

... 表示:這個位置展開為多個參數(shù)。

3.4 sizeof...運算符

sizeof...運算符用來計算參數(shù)包中參數(shù)的個數(shù)。

#include <iostream>
using namespace std;
template <class... Args>
void Print(Args&&... args)
{
	cout << sizeof...(args) << endl;
}
int main()
{
	double x = 2.2;
	Print();						// 包里有0個參數(shù)?
	Print(1);						// 包里有1個參數(shù)?
	Print(1, string("xxxxx"));		// 包里有2個參數(shù)?
	Print(1.1, string("xxxxx"), x); // 包里有3個參數(shù)?
	return 0;
}

運行結(jié)果:

0

1

2

3

3.5 包擴展

對于一個參數(shù)包,我們該如何使用里面的參數(shù)呢?包擴展就是解決這個問題的。

包擴展就是把參數(shù)包里的每個元素依次取出來,按照某種模式展開。

template<class... Args>
void Func(Args... args)
{
    Print(args...);
}
調(diào)用:
Func(1,2,3);
推導:
Args = <int,int,int>
args = <1,2,3>
Print(args...);  這里的args... 就是包擴展
Print(1,2,3);

最經(jīng)典的例子 ---- 不常用

#include <iostream>
using namespace std;
template<class... Args>
void Func(Args... args)
{
    // Args 推導出的類型必須相同或者可以強轉(zhuǎn)才能放到數(shù)組中(如double,char)
    int arr[] = { args... };
	for(int i = 0; i < sizeof...(args); ++i)
	{
		cout << arr[i] << " ";
	}
	cout << endl;
}
int main()
{
	Func(1,2,3);
	return 0;
}

遞歸展開參數(shù)包 ---- C++11

#include <iostream>
using namespace std;
void ShowList()
{
	// 編譯器時遞歸的終止條件,參數(shù)包是0個時,直接匹配這個函數(shù)?
	cout << endl;
}
template <class T, class... Args>
void ShowList(T x, Args... args)
{
	cout << x << " ";
	ShowList(args...);
}
// 編譯時遞歸推導解析參數(shù)?
template <class... Args>
void Print(Args... args)
{
    // args是N個參數(shù)的參數(shù)包?
	// 調(diào)用ShowList,參數(shù)包的第一個傳給x,剩下N-1傳給第二個參數(shù)包?
	ShowList(args...);
}
int main()
{
	Print(1, string("xxxxx"), 2.2);
	return 0;
}

函數(shù)調(diào)用展開 ---- C++11

#include <iostream>
using namespace std;
template <class T>
const T &GetArg(const T &x)
{
	cout << x << " ";
	return x;
}
template <class... Args>
void Arguments(Args... args)
{
	cout << endl;
}
template <class... Args>
void Print(Args... args)
{
	// 注意GetArg必須返回或者到的對象,這樣才能組成參數(shù)包給Arguments?
	Arguments(GetArg(args)...);
}
int main()
{
	Print(1, 'a', string("1111"), 1.1);
	return 0;
}

折疊表達式 ---- C++17

#include <iostream>
using namespace std;
template<class... Args>
void ShowList(Args... args)
{
    ((cout << args << " "), ...);
    cout << endl;
}
int main()
{
	ShowList(1, 'a', string("1111"), 1.1);
	return 0;
}

3.6 可變參數(shù)模板的原理

總結(jié):

模板:一個函數(shù)模板實例化出多個不同類型參數(shù)的函數(shù)

可變參數(shù)模板:一個可變參數(shù)函數(shù)模板實例化出多個不同參數(shù)個數(shù)的函數(shù)模板,進而實例化出多個不同類型參數(shù)的函數(shù)。

四、 emplace系列接口

4.1 emplace的意義

在C++11以后,STL容器新增了emplace系列的接口,emplace系列的接口均為可變參數(shù)模板,功能上兼容push和insert系列,但emplace還支持新的特性 ---- 可以直接在容器空間上構(gòu)造對象,在某些場景下,效率會高于push和insert系列。推薦使用emplace系列替代insert和push系列。

4.2 emplace的接口

template <class... Args> void emplace_back (Args&&... args);
template <class... Args> iterator emplace (const_iterator position,Args&&... args);

4.3 emplace系列與push和insert系列的區(qū)別

vector<string> v;
string s1 = "111111";
// 傳左值,效果一樣,均是拷貝構(gòu)造
v.push_back(s1);
v.emplace_back(s1);
// 傳右值,效果一樣,均是移動構(gòu)造
v.push_back(string("2222"));
v.emplace_back(string("2222"));
// 傳需要隱式類型轉(zhuǎn)換的對象
v.push_back("3333");
v.emplace_back("4444");
// vector 中的push_back函數(shù)
// void push_back (const value_type& val);
// void push_back (value_type&& val);
// 對于push_back,v 已經(jīng)進行實例化,value_type已經(jīng)被推導為 string
// 所以對于傳過來 const char* 對象會構(gòu)造出一個臨時對象string("3333")
// 這個臨時對象再去移動構(gòu)造 v 中的對象
// template <class... Args>
// void emplace_back (Args&&... args);
// 對于emplace_back, v 的實例化不會影響可變參數(shù)模板,所以傳過來 const char*
// 會推導為 const char* && 來直接構(gòu)造 v 中的對象
// 多參數(shù)對象
// 直接傳左值和右值,push_back 和 emplace_back 效率一樣
// 對于隱式類型轉(zhuǎn)換的參數(shù)
vector<pair<string, string>> arr;
arr.push_back({"apple", "蘋果"});
arr.emplace_back("sort", "排序");
// emplace_back 為構(gòu)造,push_back 為構(gòu)造 + 移動構(gòu)造
// 值得注意一點:在emplace_back中會通過完美轉(zhuǎn)發(fā)將參數(shù)的屬性原封不動地傳下去

總結(jié):

push_back 和 emplace_back 的區(qū)別

push_back 接收的是已經(jīng)構(gòu)造好的對象。

emplace_back 接收的是構(gòu)造對象所需的參數(shù)。

因此:

push_back 是“先構(gòu)造對象,再放入容器”;
emplace_back 是“直接在容器內(nèi)部構(gòu)造對象”。

什么時候效率一樣?

當對象已經(jīng)存在時,兩者基本沒有區(qū)別。

例如已經(jīng)有一個字符串對象,無論使用 push_back 還是 emplace_back,本質(zhì)上都是拷貝或移動這個對象。

什么時候 emplace_back 更有優(yōu)勢?

當傳入的不是對象,而是構(gòu)造對象所需的參數(shù)時。

此時 push_back 往往需要先生成一個臨時對象,再放入容器;而 emplace_back 可以直接在容器內(nèi)部構(gòu)造目標對象,減少一次中間過程。

為什么 emplace_back 能做到?

因為它使用了:

  • 可變參數(shù)模板
  • 完美轉(zhuǎn)發(fā)
  • 原地構(gòu)造

它會把收到的參數(shù)原封不動地轉(zhuǎn)交給元素類型的構(gòu)造函數(shù),并直接在容器分配好的內(nèi)存上構(gòu)造對象。

多參數(shù)對象是 emplace 的最佳場景

對于需要多個參數(shù)才能構(gòu)造的對象,例如鍵值對、映射節(jié)點、自定義類等:

emplace 可以直接把這些參數(shù)傳給構(gòu)造函數(shù),而 push 系列通常需要先構(gòu)造一個完整對象再插入。

因此優(yōu)勢最明顯。

面試高頻總結(jié)

  • push_back 與 emplace_back 的本質(zhì)區(qū)別是什么?
  • push_back 插入對象,emplace_back 構(gòu)造對象。
  • emplace_back 一定比 push_back 快嗎?
  • 不一定。對象已經(jīng)存在時,兩者通常沒有明顯區(qū)別。
  • 什么時候應該優(yōu)先使用 emplace_back?
  • 當需要根據(jù)參數(shù)現(xiàn)場構(gòu)造對象時。
  • emplace_back 底層依賴什么技術?
  • 可變參數(shù)模板、完美轉(zhuǎn)發(fā)和原地構(gòu)造。
  • 為什么 STL 后來增加了 emplace 系列接口?
  • 為了減少不必要的臨時對象,提高構(gòu)造復雜對象時的效率。

到此這篇關于C++11 引用折疊、完美轉(zhuǎn)發(fā)、可變模板參數(shù)、emplace系列接口詳解的文章就介紹到這了,更多相關C++11 引用折疊內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

  • C語言實現(xiàn)簡單圖書管理系統(tǒng)

    C語言實現(xiàn)簡單圖書管理系統(tǒng)

    這篇文章主要為大家詳細介紹了C語言實現(xiàn)圖書管理系統(tǒng),文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2018-01-01
  • C/C++自定義類型結(jié)構(gòu)體全解析

    C/C++自定義類型結(jié)構(gòu)體全解析

    c語言有內(nèi)置類型(char?short?int?long?flaot?double?long?double),也有自定義類型—結(jié)構(gòu)體(struct)?枚舉(enum)?聯(lián)合體(union)?本文介紹結(jié)構(gòu)體,感興趣的朋友跟隨小編一起看看吧
    2025-05-05
  • Qt中QUdpSocket類的簡單使用詳解

    Qt中QUdpSocket類的簡單使用詳解

    文章介紹了QUdpSocket類的基本使用方法,包括發(fā)送端和接收端的代碼實現(xiàn),QUdpSocket是Qt中用于UDP通信的類,它支持無連接通信、廣播和數(shù)據(jù)包的發(fā)送與接收,文章還提到了數(shù)據(jù)編碼和對比TCP的簡化點,感興趣的朋友跟隨小編一起看看吧
    2026-01-01
  • C語言實現(xiàn)簡單的飛機大戰(zhàn)游戲

    C語言實現(xiàn)簡單的飛機大戰(zhàn)游戲

    這篇文章主要為大家詳細介紹了C語言實現(xiàn)簡單的飛機大戰(zhàn)游戲,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2022-05-05
  • C++控制臺繪圖頭文件實例代碼

    C++控制臺繪圖頭文件實例代碼

    控制臺(console)是電腦的最基本交互接口,通常包括鍵盤(keyboard)和屏幕(screen),下面這篇文章主要給大家介紹了關于C++控制臺繪圖頭文件的相關資料,文中通過示例代碼介紹的非常詳細,需要的朋友可以參考下
    2023-01-01
  • C語言基礎雙指針移除元素解法

    C語言基礎雙指針移除元素解法

    這篇文章介紹了C語言基礎雙指針移除元素的解法,文中通過示例代碼介紹的非常詳細。對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2021-12-12
  • C語言中文件讀取中文亂碼問題解析與解決方案

    C語言中文件讀取中文亂碼問題解析與解決方案

    在C語言編程中,文件操作是常見任務之一,然而,當讀取包含中文的文本文件時,開發(fā)者常常會遇到?"燙燙燙"亂碼?或?中文顯示異常?的問題,本文將深入分析這些問題的根源,并提供完整的解決方案,需要的朋友可以參考下
    2025-05-05
  • C++基礎入門教程(三):數(shù)組、字符串、結(jié)構(gòu)體、共用體

    C++基礎入門教程(三):數(shù)組、字符串、結(jié)構(gòu)體、共用體

    這篇文章主要介紹了C++基礎入門教程(三):數(shù)組、字符串、結(jié)構(gòu)體、共用體,需要的朋友可以參考下
    2014-11-11
  • 詳解C++的JSON靜態(tài)鏈接庫JsonCpp的使用方法

    詳解C++的JSON靜態(tài)鏈接庫JsonCpp的使用方法

    這篇文章主要介紹了C++的JSON靜態(tài)鏈接庫JsonCpp的使用方法,演示了使用JsonCpp生成和解析JSON的方法,以及C++通過JSON方式的socket通信示例,需要的朋友可以參考下
    2016-03-03
  • C語言實現(xiàn)隨機抽獎程序

    C語言實現(xiàn)隨機抽獎程序

    這篇文章主要為大家詳細介紹了C語言實現(xiàn)隨機抽獎程序,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2021-09-09

最新評論

尉氏县| 中阳县| 棋牌| 辰溪县| 阜平县| 萨嘎县| 邵阳市| 兴业县| 句容市| 安顺市| 米泉市| 房产| 威海市| 肥东县| 萨迦县| 康定县| 砚山县| 新干县| 祁连县| 射洪县| 栾川县| 云南省| 双牌县| 恩施市| 色达县| 华池县| 睢宁县| 贵溪市| 汶川县| 合川市| 河池市| 文化| 舟曲县| 田林县| 陵川县| 章丘市| 丰原市| 青河县| 荔波县| 梁河县| 昌黎县|