C++?11新特性之右值引用使用案例與應(yīng)用場(chǎng)景
概述
C++ 11中引入了一項(xiàng)關(guān)鍵特性——右值引用,極大地增強(qiáng)了C++在資源管理、性能優(yōu)化和表達(dá)力方面的能力。通過(guò)理解并合理運(yùn)用右值引用,我們可以編寫出更高效、更簡(jiǎn)潔且不易出錯(cuò)的代碼。本文將深入探討右值引用的概念、工作原理及其在C++編程實(shí)踐中的應(yīng)用場(chǎng)景。
右值引用是C++中的一種特殊引用類型,它只能綁定到臨時(shí)對(duì)象或即將銷毀的對(duì)象上,也就是那些沒(méi)有命名且不再需要的對(duì)象。語(yǔ)法上,右值引用以&&表示,可參考下面的示例代碼。
// 右值引用,綁定到字面量整數(shù) int&& nData = 42;
工作原理
理解右值引用是學(xué)習(xí)“移動(dòng)語(yǔ)義”的基礎(chǔ),而要理解右值引用,就必須先區(qū)分左值與右值。
對(duì)左值和右值的一個(gè)最常見的誤解是:等號(hào)左邊的就是左值,等號(hào)右邊的就是右值。左值和右值都是針對(duì)表達(dá)式而言的,左值是指表達(dá)式結(jié)束后依然存在的持久對(duì)象,右值是指表達(dá)式結(jié)束時(shí)就不再存在的臨時(shí)對(duì)象。一個(gè)區(qū)分左值與右值的便捷方法是:看能不能對(duì)表達(dá)式取地址,如果能,則為左值,否則為右值。下面我們結(jié)合一些實(shí)際例子來(lái)進(jìn)行說(shuō)明。
int a = 100; int b = 200; int *pFlag = &a; vector<int> vctTemp; vctTemp.push_back(66); string str1 = "Hello "; string str2 = "World"; const int &m = 99;
請(qǐng)問(wèn):a、b、a+b、a++、++a、pFlag、*pFlag、vctTemp[0]、100、string("CSDN")、str1、str1+str2、m分別是左值還是右值?
1、a和b都是持久對(duì)象(可以對(duì)其取地址),是左值。
2、a+b是臨時(shí)對(duì)象(不可以對(duì)其取地址),是右值。
3、a++是先取出持久對(duì)象a的一份拷貝,再使持久對(duì)象a的值加1,最后返回那份拷貝,而那份拷貝是臨時(shí)對(duì)象(不可以對(duì)其取地址),故其是右值。
4、++a則是使持久對(duì)象a的值加1,并返回那個(gè)持久對(duì)象a本身(可以對(duì)其取地址),故其是左值。
5、pFlag和*pFlag都是持久對(duì)象(可以對(duì)其取地址),是左值。
6、vctTemp[0]調(diào)用了重載的[]操作符,而[]操作符返回的是一個(gè)int &,為持久對(duì)象(可以對(duì)其取地址),是左值。
7、100和string("CSDN")是臨時(shí)對(duì)象(不可以對(duì)其取地址),是右值。
8、str1是持久對(duì)象(可以對(duì)其取地址),是左值。
9、str1+str2是調(diào)用了+操作符,而+操作符返回的是一個(gè)string(不可以對(duì)其取地址),故其為右值。
10、m是一個(gè)常量引用,引用到一個(gè)右值,但引用本身是一個(gè)持久對(duì)象(可以對(duì)其取地址),為左值。
區(qū)分清楚了左值與右值,我們?cè)賮?lái)看看左值引用。左值引用根據(jù)其修飾符的不同,可以分為非常量左值引用和常量左值引用。
非常量左值引用只能綁定到非常量左值,不能綁定到常量左值、非常量右值和常量右值。如果允許綁定到常量左值和常量右值,則非常量左值引用可以用于修改常量左值和常量右值,這明顯違反了其常量的含義。如果允許綁定到非常量右值,則會(huì)導(dǎo)致非常危險(xiǎn)的情況出現(xiàn)。因?yàn)榉浅A坑抑凳且粋€(gè)臨時(shí)對(duì)象,非常量左值引用可能會(huì)使用一個(gè)已經(jīng)被銷毀了的臨時(shí)對(duì)象。
常量左值引用可以綁定到所有類型的值,包括:非常量左值、常量左值、非常量右值和常量右值。
可以看出,使用左值引用時(shí),我們無(wú)法區(qū)分出綁定的是否是非常量右值的情況。那么,為什么要對(duì)非常量右值進(jìn)行區(qū)分呢?區(qū)分出來(lái)了,又有什么好處呢?這就牽涉到C++中一個(gè)著名的性能問(wèn)題——拷貝臨時(shí)對(duì)象??紤]下面的示例代碼。
vector<int> GetAllScores()
{
vector<int> vctTemp;
vctTemp.push_back(90);
vctTemp.push_back(95);
return vctTemp;
}當(dāng)使用vector<int> vctScore = GetAllScores()進(jìn)行初始化時(shí),實(shí)際上調(diào)用了三次構(gòu)造函數(shù)。盡管有些編譯器可以采用RVO(Return Value Optimization)來(lái)進(jìn)行優(yōu)化,但優(yōu)化工作只在某些特定條件下才能進(jìn)行??梢钥吹剑厦婧芷胀ǖ囊粋€(gè)函數(shù)調(diào)用,由于存在臨時(shí)對(duì)象的拷貝,導(dǎo)致了額外的兩次拷貝構(gòu)造函數(shù)和析構(gòu)函數(shù)的開銷。當(dāng)然,我們也可以修改函數(shù)的形式為:void GetAllScores(vector<int> &vctScore),但這并不一定就是我們需要的形式。另外,考慮下面字符串的連接操作:
string s1("Hello ");
string s = s1 + "W" + "o" + "r" + "l" + "d";在對(duì)s進(jìn)行初始化時(shí),會(huì)產(chǎn)生大量的臨時(shí)對(duì)象,并涉及到大量字符串的拷貝操作,這顯然會(huì)影響程序的效率和性能。怎么解決這個(gè)問(wèn)題呢?如果我們能確定某個(gè)值是一個(gè)非常量右值(或者是一個(gè)以后不會(huì)再使用的左值),則我們?cè)谶M(jìn)行臨時(shí)對(duì)象的拷貝時(shí),可以不用拷貝實(shí)際的數(shù)據(jù),而只是“竊取”指向?qū)嶋H數(shù)據(jù)的指針(類似于STL中的auto_ptr,會(huì)轉(zhuǎn)移所有權(quán))。C++ 11中引入的右值引用,正好可用于標(biāo)識(shí)一個(gè)非常量右值。C++ 11中用&表示左值引用,用&&表示右值引用,比如:int &&a = 10。
右值引用根據(jù)其修飾符的不同,也可以分為:非常量右值引用和常量右值引用。
非常量右值引用只能綁定到非常量右值,不能綁定到非常量左值、常量左值和常量右值。如果允許綁定到非常量左值,則可能會(huì)錯(cuò)誤地竊取一個(gè)持久對(duì)象的數(shù)據(jù),而這是非常危險(xiǎn)的;如果允許綁定到常量左值和常量右值,則非常量右值引用可以用于修改常量左值和常量右值,這明顯違反了其常量的含義。
常量右值引用可以綁定到非常量右值和常量右值,不能綁定到非常量左值和常量左值(理由同上)。
使用案例
有了右值引用的概念,我們可以用它來(lái)實(shí)現(xiàn)下面的CMyString類。
#include <iostream>
using namespace std;
class CMyString
{
public:
// 構(gòu)造函數(shù)
CMyString(const char *pszSrc = NULL)
{
cout << "CMyString(const char *pszSrc = NULL)" << endl;
if (pszSrc == NULL)
{
m_pData = new char[1];
*m_pData = '\0';
}
else
{
m_pData = new char[strlen(pszSrc) + 1];
strcpy(m_pData, pszSrc);
}
}
// 拷貝構(gòu)造函數(shù)
CMyString(const CMyString &s)
{
cout << "CMyString(const CMyString &s)" << endl;
m_pData = new char[strlen(s.m_pData) + 1];
strcpy(m_pData, s.m_pData);
}
// move構(gòu)造函數(shù)
CMyString(CMyString &&s)
{
cout << "CMyString(CMyString &&s)" << endl;
m_pData = s.m_pData;
s.m_pData = NULL;
}
// 析構(gòu)函數(shù)
~CMyString()
{
cout << "~CMyString()" << endl;
delete [] m_pData;
m_pData = NULL;
}
// 拷貝賦值函數(shù)
CMyString &operator =(const CMyString &s)
{
cout << "CMyString &operator =(const CMyString &s)" << endl;
if (this != &s)
{
delete [] m_pData;
m_pData = new char[strlen(s.m_pData) + 1];
strcpy(m_pData, s.m_pData);
}
return *this;
}
// move賦值函數(shù)
CMyString &operator =(CMyString &&s)
{
cout << "CMyString &operator =(CMyString &&s)" << endl;
if (this != &s)
{
delete [] m_pData;
m_pData = s.m_pData;
s.m_pData = NULL;
}
return *this;
}
private:
char *m_pData;
};
int main()
{
CMyString strText("Hello CSDN");
CMyString strText2;
strText2 = CMyString("BeiJing");
return 0;
}可以看到,上面我們添加了move版本的構(gòu)造函數(shù)和賦值函數(shù)。那么,添加了move版本后,對(duì)類的自動(dòng)生成規(guī)則有什么影響呢?唯一的影響就是,如果提供了move版本的構(gòu)造函數(shù),則不會(huì)生成默認(rèn)的構(gòu)造函數(shù)。另外,編譯器永遠(yuǎn)不會(huì)自動(dòng)生成move版本的構(gòu)造函數(shù)和賦值函數(shù),它們需要你手動(dòng)顯式地添加。
當(dāng)添加了move版本的構(gòu)造函數(shù)和賦值函數(shù)的重載形式后,某一個(gè)函數(shù)調(diào)用應(yīng)當(dāng)使用哪一個(gè)重載版本呢?下面是按照判決的優(yōu)先級(jí)列出的3條規(guī)則。
1、常量值只能綁定到常量引用上,不能綁定到非常量引用上。
2、左值優(yōu)先綁定到左值引用上,右值優(yōu)先綁定到右值引用上。
3、非常量值優(yōu)先綁定到非常量引用上。
當(dāng)給構(gòu)造函數(shù)或賦值函數(shù)傳入一個(gè)非常量右值時(shí),依據(jù)上面給出的判決規(guī)則,可以得出會(huì)調(diào)用move版本的構(gòu)造函數(shù)或賦值函數(shù)。而在move版本的構(gòu)造函數(shù)或賦值函數(shù)內(nèi)部,都是直接“移動(dòng)”了其內(nèi)部數(shù)據(jù)的指針。因?yàn)樗欠浅A坑抑?,是一個(gè)臨時(shí)對(duì)象,移動(dòng)了其內(nèi)部數(shù)據(jù)的指針不會(huì)導(dǎo)致任何問(wèn)題,它馬上就要被銷毀了,我們只是重復(fù)利用了其內(nèi)存,這樣就省去了拷貝數(shù)據(jù)的大量開銷。
一個(gè)需要注意的地方是:拷貝構(gòu)造函數(shù)可以通過(guò)直接調(diào)用*this = s來(lái)實(shí)現(xiàn),但move構(gòu)造函數(shù)卻不能。這是因?yàn)樵趍ove構(gòu)造函數(shù)中,s雖然是一個(gè)非常量右值引用,但其本身卻是一個(gè)左值(是持久對(duì)象,可以對(duì)其取地址),因此調(diào)用*this = s時(shí),會(huì)使用拷貝賦值函數(shù)而不是move賦值函數(shù),而這已與move構(gòu)造函數(shù)的語(yǔ)義不相符。要使語(yǔ)義正確,我們需要將左值綁定到非常量右值引用上,C++ 11提供了move函數(shù)來(lái)實(shí)現(xiàn)這種轉(zhuǎn)換,因此我們可以修改為*this = move(s),這樣move構(gòu)造函數(shù)就會(huì)調(diào)用move賦值函數(shù)。
應(yīng)用場(chǎng)景
1、智能指針。
C++ 11標(biāo)準(zhǔn)庫(kù)中的std::unique_ptr和std::shared_ptr等智能指針利用了右值引用來(lái)安全有效地轉(zhuǎn)移所有權(quán)。
2、容器。
C++ STL容器,比如:std::vector、std::string等,在C++11之后都開始支持移動(dòng)構(gòu)造和移動(dòng)賦值,從而避免了在添加元素或重新分配內(nèi)存時(shí)不必要的數(shù)據(jù)拷貝。
3、字符串字面值。
C++ 11中引入了std::string_view,它可以通過(guò)右值引用高效地引用字符串字面值,或現(xiàn)有std::string對(duì)象的內(nèi)容,而無(wú)需復(fù)制。
4、RAII原則強(qiáng)化。
RAII,也就是資源獲取即初始化(英文為:Resource Acquisition Is Initialization),是C++中一種重要的資源管理手段。右值引用使資源在生命周期結(jié)束時(shí)能被自動(dòng)回收,并允許在對(duì)象之間高效地“移動(dòng)”資源控制權(quán)。
總結(jié)
右值引用無(wú)疑是C++ 11的一項(xiàng)重大革新,它不僅提高了程序的運(yùn)行效率,也極大地簡(jiǎn)化了程序員對(duì)資源管理的復(fù)雜度。掌握這一特性對(duì)于寫出更現(xiàn)代、更高效的C++代碼至關(guān)重要。同時(shí),隨著C++后續(xù)版本的發(fā)展,右值引用在標(biāo)準(zhǔn)庫(kù)中的使用越來(lái)越廣泛,成為現(xiàn)代C++開發(fā)者的必備知識(shí)之一。
到此這篇關(guān)于C++ 11新特性之右值引用使用案例與應(yīng)用場(chǎng)景的文章就介紹到這了,更多相關(guān)C++ 11新特性右值引用內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
詳解MFC/C++調(diào)用易語(yǔ)言的整數(shù)型和文本型與VS2010互動(dòng)
在本篇文章里我們給大家分享了MFC/C++調(diào)用易語(yǔ)言的整數(shù)型和文本型與VS2010互動(dòng)相關(guān)知識(shí)點(diǎn)內(nèi)容,有興趣的朋友們可以參考下。2018-11-11
C語(yǔ)言代碼實(shí)現(xiàn)簡(jiǎn)單掃雷小游戲
這篇文章主要為大家詳細(xì)介紹了C語(yǔ)言實(shí)現(xiàn)掃雷游戲,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-01-01
C++實(shí)現(xiàn)的一個(gè)可以寫遞歸lambda的Y函數(shù)
這篇文章主要介紹了C++實(shí)現(xiàn)的一個(gè)可以寫遞歸lambda的Y函數(shù),在Y函數(shù)的幫助,這個(gè)lambda表達(dá)是可以成功看到自己,然后遞歸調(diào)用的,需要的朋友可以參考下2014-07-07
詳解C語(yǔ)言中結(jié)構(gòu)體(struct)的用法
這篇文章主要為大家詳細(xì)介紹了C語(yǔ)言中結(jié)構(gòu)體(struct)的用法,文中的示例代碼講解詳細(xì),對(duì)我們學(xué)習(xí)C語(yǔ)言有一定幫助,需要的可以參考一下2022-08-08
VScode編譯C++ 頭文件顯示not found的問(wèn)題
這篇文章主要介紹了VScode編譯C++ 頭文件顯示not found的問(wèn)題,本文給大家分享問(wèn)題解決方法,通過(guò)截圖的形式給大家介紹的非常詳細(xì),需要的朋友可以參考下2020-03-03

