溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

c++右值引用和移動(dòng)構(gòu)造是什么

發(fā)布時(shí)間:2022-03-25 09:28:27 來(lái)源:億速云 閱讀:107 作者:iii 欄目:互聯(lián)網(wǎng)科技

本文小編為大家詳細(xì)介紹“c++右值引用和移動(dòng)構(gòu)造是什么”,內(nèi)容詳細(xì),步驟清晰,細(xì)節(jié)處理妥當(dāng),希望這篇“c++右值引用和移動(dòng)構(gòu)造是什么”文章能幫助大家解決疑惑,下面跟著小編的思路慢慢深入,一起來(lái)學(xué)習(xí)新知識(shí)吧。

C++ 右值引用 & 新特性
C++ 11中引入的一個(gè)非常重要的概念就是右值引用。理解右值引用是學(xué)習(xí)“移動(dòng)語(yǔ)義”(move semantics)的基礎(chǔ)。而要理解右值引用,就必須先區(qū)分左值與右值。

對(duì)左值和右值的一個(gè)最常見(jiàn)的誤解是:等號(hào)左邊的就是左值,等號(hào)右邊的就是右值。左值和右值都是針對(duì)表達(dá)式而言的,左值是指表達(dá)式結(jié)束后依然存在的持久對(duì)象,右值是指表達(dá)式結(jié)束時(shí)就不再存在的臨時(shí)對(duì)象。一個(gè)區(qū)分左值與右值的便捷方法是:看能不能對(duì)表達(dá)式取地址,如果能,則為左值,否則為右值。下面給出一些例子來(lái)進(jìn)行說(shuō)明。

 int a = 10;
 int b = 20;
 int *pFlag = &a;
 vector<int> vctTemp;
 vctTemp.push_back(1);
 string str1 = "hello ";
 string str2 = "world";
 const int &m = 1;
請(qǐng)問(wèn),a,b, a+b, a++, ++a, pFlag, *pFlag, vctTemp[0], 100, string("hello"), str1, str1+str2, m分別是左值還是右值?
a和b都是持久對(duì)象(可以對(duì)其取地址),是左值;
a+b是臨時(shí)對(duì)象(不可以對(duì)其取地址),是右值;
a++是先取出持久對(duì)象a的一份拷貝,再使持久對(duì)象a的值加1,最后返回那份拷貝,而那份拷貝是臨時(shí)對(duì)象(不可以對(duì)其取地址),故其是右值;
++a則是使持久對(duì)象a的值加1,并返回那個(gè)持久對(duì)象a本身(可以對(duì)其取地址),故其是左值;
pFlag和*pFlag都是持久對(duì)象(可以對(duì)其取地址),是左值;
vctTemp[0]調(diào)用了重載的[]操作符,而[]操作符返回的是一個(gè)int &,為持久對(duì)象(可以對(duì)其取地址),是左值;
100和string("hello")是臨時(shí)對(duì)象(不可以對(duì)其取地址),是右值;
str1是持久對(duì)象(可以對(duì)其取地址),是左值;
str1+str2是調(diào)用了+操作符,而+操作符返回的是一個(gè)string(不可以對(duì)其取地址),故其為右值;
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ù)(一次是vecTemp的構(gòu)造,一次是return 臨時(shí)對(duì)象的構(gòu)造,一次是vecScore的復(fù)制構(gòu)造)。盡管有些編譯器可以采用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ù)的開(kāi)銷。當(dāng)然,我們也可以修改函數(shù)的形式為void GetAllScores(vector<int> &vctScore),但這并不一定就是我們需要的形式。另外,考慮下面字符串的連接操作:

 string s1("hello");
 string s = s1 + "a" + "b" + "c" + "d" + "e";
在對(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類。

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ù)     ----        實(shí)質(zhì)上就是·竊取·臨時(shí)對(duì)象,注意參數(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;
};
可以看到,上面我們添加了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ù)的大量開(kāi)銷。

一個(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ù)。

讀到這里,這篇“c++右值引用和移動(dòng)構(gòu)造是什么”文章已經(jīng)介紹完畢,想要掌握這篇文章的知識(shí)點(diǎn)還需要大家自己動(dòng)手實(shí)踐使用過(guò)才能領(lǐng)會(huì),如果想了解更多相關(guān)內(nèi)容的文章,歡迎關(guān)注億速云行業(yè)資訊頻道。

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

c++
AI