国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

MordernC++之左值(引用)與右值(引用)

這篇具有很好參考價值的文章主要介紹了MordernC++之左值(引用)與右值(引用)。希望對大家有所幫助。如果存在錯誤或未考慮完全的地方,請大家不吝賜教,您也可以點擊"舉報違法"按鈕提交疑問。

左值與右值

C++中左值與右值的概念是從C中繼承而來,一種簡單的定義是左值能夠出現(xiàn)再表達(dá)式的左邊或者右邊,而右值只能出現(xiàn)在表達(dá)式的右邊。

int a = 5; 	// a是左值,5是右值 
int b = a;	// b是左值,a也是左值
int c = a + b;	// c是左值,a + b是右值

另一種區(qū)分左值和右值的方法是:有名字、能取地址的值是左值,沒有名字、不能取地址的值是右值。比如上述語句中a,b, c是變量可以取地址,所以是左值,而5和a + b無法進行取地址操作,因此是右值。C++中左值與右值的一個主要的區(qū)別是:左值可以被修改,而右值不可修改

左值引用與右值引用

了解了左值與右值的概念后,接下來介紹下C++中的左值引用與右值引用。左值引用很簡單,就是一個變量的別名,綁定到一個左值上:

int a = 1;
int& b = a; 	//a = 1,b = 1
b = 2;		// a = 2,b = 2

這里b就等于a,在匯編層面其實和普通的指針一樣,對引用的修改(b)也會修改到被引用的對象(a),需要注意的是,因為引用實際是一個別名,因此必須初始化,即告訴編譯器是那個具體對象的別名。因此下列左值引用都是錯誤的:

int& a;		// 錯誤!左值引用必須初始化
int& b = 10;	// 錯誤!左值引用不能以臨時變量初始化(臨時變量沒有地址)

右值引用是C++11中新增的特性,顧名思義,右值引用就是用來綁定到右值的引用,一個右值被綁定到右值引用之后,原本需要被銷毀的此右值生命周期會延長至綁定它的右值引用的生命周期。在匯編層面,右值引用和const引用所做的事情是一樣的,即產(chǎn)生臨時量來存儲常量。但是右值引用可以進行讀寫操作,而const引用只能進行讀操作。綁定右值引用使用&&,具體使用如下:

int a = 5;
int& b = a;		// 正確!b是一個左值引用
int&& c = 6;		// 正確!c是一個右值引用,綁定到右值6
int&& d = a * 2;	// 正確!d是一個右值引用,綁定到右值a * 2
int&& e = i;		// 錯誤!不能將左值綁定到右值引用
int& f = 7;		// 錯誤!不能將右值綁定到左值引用
const int& g = a * 3;	// 正確!可以將右值綁定到const 左值引用

可以看到我們雖然不能將右值綁定到左值引用,但是可以將右值綁定到const左值引用。
注意: 變量表達(dá)式都是左值!。變量可以看作是只有一個運算對象而沒有運算符的表達(dá)式,跟其他表達(dá)式一樣,變量表達(dá)式也有左值/右值屬性。變量表達(dá)式都是左值,因此我們不能將一個右值引用綁定到一個右值引用類型的變量上。

int&& a = 5;	// 正確!a是一個右值引用
int&& b = a;	// 錯誤!a是一個左值,不能綁定到右值引用

這里雖然a是右值引用類型,但是確實一個左值,因此無法綁定到右值引用b上。因為在C++中,右值一般是臨時對象,但是綁定到右值引用之后,其生命周期變長了,因此a是一個左值。我們不能將一個右值引用直接綁定到一個變量上,即使是這個變量是右值引用類型也不行。具體的這個問題在后續(xù)的介紹forward的時候會詳細(xì)說明。

左值/右值引用的模板實參推斷

在另一篇文章中介紹了C++的模板類型推斷的幾種類型,可以總結(jié)為以下三種:

  1. ParamType 是一個指針或者引用(&),但是不是通用引用(&&)
  2. ParamType是一個通用引用(&&)
  3. ParamType既不是指針,也不是引用(&)或者通用引用(&&)

從左值引用函數(shù)參數(shù)推斷類型

當(dāng)一個函數(shù)參數(shù)是模板的左值引用(T&)時,根據(jù)綁定規(guī)則,只能傳遞一個左值實參,這個左值實參可以時const類型,也可以不是。如果實參時const的,那么T就會被推導(dǎo)為const類型

template<typename T>
void func(T& param);

int a = 0;
const int b = a;
func(a);	// T被推導(dǎo)為int,param類型為int&
func(b);	// T被推導(dǎo)為const int,param類型為const int&
func(5);	// 錯誤!實參必須是一個左值!

如果一個函數(shù)的類型時const T&,那么根據(jù)綁定規(guī)則,可以傳遞任何類型的實參:const或者非const,左值或者右值,由于函數(shù)類型本身已經(jīng)是const,因此T的推導(dǎo)結(jié)果不會是一個const,因為const已經(jīng)是函數(shù)參數(shù)類型的一部分了。

template<typename T>
void func(const T& param);

int a = 0;
const int b = a;
func(a);	// T被推導(dǎo)為int,param類型為const int&
func(b);	// T被推導(dǎo)為int,param類型為const int&
func(5);	// 正確!const T&可以綁定一個右值,T為int

可以看到,當(dāng)函數(shù)參數(shù)類型為const T&時,可以接受一個右值實參,而函數(shù)參數(shù)類型為 T& 時是不可以的。

從右值引用函數(shù)參數(shù)推斷類型

當(dāng)一個函數(shù)的參數(shù)是一個右值引用(T&&)時,根據(jù)綁定規(guī)則可以傳遞一個右值實參。類似左值引用推導(dǎo),右值引用推導(dǎo)得到的T的類型為右值的類型:

template<typename T>
void func(T&& param);

func(5);	// 實參5為右值,T被推導(dǎo)為int類型

與不能給右值引用賦值左值不同,右值引用函數(shù)的模板實參卻可以接受一個左值的輸入。當(dāng)我們將一個左值傳遞給函數(shù)的右值引用參數(shù)時,且此右值引用指向模板參數(shù)類型(T&&)時,編譯器推導(dǎo)模板類型參數(shù)為實參的左值引用類型:

template<typename T>
void func(T&& param);

int a = 1;
func(a);	// T被推導(dǎo)為int&,而不是int

如上述推導(dǎo)所示,當(dāng)傳入一個左值a時,T被推導(dǎo)為int&,而不是int,對應(yīng)的param的類型為int& &&,根據(jù)引用折疊的規(guī)則,int& &&被折疊為int&。

引用折疊規(guī)則
T& & ,T& && 和T&& &都會被折疊為T&
T&& &&被折疊為T&&

引用折疊的規(guī)則告訴我們:如果一個函數(shù)的參數(shù)時指向模板參數(shù)類型的右值引用(如T&&),則可以傳遞給它任意類型的實參,如果傳遞的左值實參,那么T將會推導(dǎo)成為一個左值引用,函數(shù)參數(shù)被實例化為一個普通的左值引用(T&)。這種引用叫做“通用引用”

右值引用與通用引用

C++中T&&有兩種不同的意思,第一種是右值引用,用于綁定到右值上,它們主要存在的原因是為了聲明某個對象可以被移動。T&&的第二層意思是,它既可以是一個右值引用,也可以是一個左值引用。這種引用在代碼里看起來像是右值引用(T&&),又可以表現(xiàn)的像是左值引用(T&)。它既可以綁定到右值,也可以綁定到左值,還可以綁定到const和no_const對象上,幾乎可以綁定到任何東西,這種引用叫做“通用引用”。在兩種情況下會出現(xiàn)通用引用,最常見的就是函數(shù)模板參數(shù):

template<typename T>
void func(T&& param); // param是一個通用引用

第二種情況是auto聲明符:

auto&& a = b;	//a是一個通用引用

以上兩種情況的共同之處在于都是類型推導(dǎo)。在func內(nèi)部,param類型需要被推導(dǎo),在auto聲明中,a的類型也需要被推導(dǎo),而如果帶有&&而不需要推導(dǎo),則就是普通的右值引用:

void func(A&& param);	// 沒有類型推導(dǎo),param是一個右值引用
A&& a = b;		// 沒有類型推導(dǎo),a是一個右值引用

由于引用必須初始化,通用引用也一樣。一個通用引用的初始值決定了其具體代表的是一個左值引用還是右值引用。如果初始值是一個左值,那么通用引用對應(yīng)的就是左值引用,如果初始值是一個右值,那么通用引用對應(yīng)的就是一個右值引用。

template<typename T>
void func(T&& param); // param是一個通用引用

int a = 1;
func(a);		// a是左值,T被推導(dǎo)為int&,參數(shù)param的類型是int&,是一個左值引用
func(5);		// 5是右值,T被推導(dǎo)為int,參數(shù)param的類型是int&&,是一個右值引用

需要注意的是,判斷一個引用是不是通用引用,類型推導(dǎo)是必要的,但是并不是類型推導(dǎo)就是通用引用,還需要看是不是準(zhǔn)確的T&&,如:

template<typename T>
void func(std::vector<T>&& param); // param是一個右值引用

template<typename T>
void func(const T&& param); // param是一個右值引用

上述模板函數(shù)func被調(diào)用的時候,類型T也會被推導(dǎo),但是參數(shù)param的類型并不是T&&,而是一個std::vector&&,因此param是一個右值引用而不是通用引用。即使多了一個const,那么param也不能成為一個通用引用。

理解std::move()

有了上述的知識基礎(chǔ)之后,C++中的move函數(shù)功能就很好理解了,std::move的主要作用是將一個左值/右值無條件的轉(zhuǎn)換為右值,但是函數(shù)本身并不移動任何東西,只是進行類型的轉(zhuǎn)換,那么這種轉(zhuǎn)換是如何做到的呢?我們來看下std::move具體實現(xiàn)的代碼:

template<class T>
typename remove_reference<T>::type&& move(T&& param)
{
    using returnType = typename remove_reference<T>::type&&;
    return static_cast<returnType>(param);
}

通過源碼可以看到,std::move接受一個通用引用的參數(shù),函數(shù)返回一個&&表明std::move函數(shù)返回的是一個右值引用,這里remove_reference表示移除類型T的引用部分,具體的實現(xiàn)可以參考文檔,即返回結(jié)果是右值。在C++14中std::move的實現(xiàn)更加簡單:

template<typename T>
decltype(auto) move(T&& param)
{
    using returnType = remove_reference_t<T>&&;
    return static_cast<returnType>(param);
}

讓我們通過以下的代碼示例具體分析下std::move是如何工作的:

string s1("hello"),s2;
s2 = std::move(string("world")); // 從右值移動數(shù)據(jù)
s2 = std::move(s1);		 // 將左值轉(zhuǎn)換為右值 

在第一個賦值中,傳遞給move的實參是一個右值,當(dāng)向一個右值引用傳遞一個右值時,推導(dǎo)的類型即被引用的類型,因此在std::move(string("world"))中:

  • T被推導(dǎo)為string
  • returnType為string
  • move的返回類型為string&&
  • move的函數(shù)參數(shù)param的類型為string&&
    則函數(shù)std::move被推導(dǎo)為:
string&& move(string&& param)
{
	return static_cast<string&&>(param);
}

由于param已經(jīng)時右值引用類型,因此實際上move函數(shù)什么也沒做。
在第二個賦值中,傳給std::move的參數(shù)是一個左值,則在std::move(s1)中:

  • T被推導(dǎo)為string&
  • returnType為string
  • move的返回類型為string&&
  • move的函數(shù)參數(shù)param的類型為string&
    則函數(shù)std::move被推導(dǎo)為:
string&& move(string& param)
{
	return static_cast<string&&>(param);
}

可以看到參數(shù)param被static_cast轉(zhuǎn)換為sting&&,在C++中,從一個左值static_cast到一個右值引用時允許的。
從以上的示例可以看到,不管傳入的是左值還是右值,最終move都會返回一個右值。

理解std::forward()

std::forward與std::move實現(xiàn)的功能是類似的,只不過std::move總是無條件的將它的參數(shù)轉(zhuǎn)換為右值,而std::forward只有在滿足一定的條件下才會執(zhí)行轉(zhuǎn)換。std::forward最常見的使用場景是一個模板函數(shù),接受一個通用引用參數(shù),并將其傳遞給另外的函數(shù):

void Process(const A& lvalue);	// 處理左值
void Process(A&& rvalue);	// 處理右值

template<typename T>
void PrintAndProcess(T&& param)
{
    Print("Some Log");
    process(std::forward<T>(param))
}

現(xiàn)在考慮兩次對PrintAndProcess的調(diào)用,一次參數(shù)為左值,一次參數(shù)為右值

A a;
PrintAndProcess(a);		// 左值參數(shù)
PrintAndProcess(std::move(a));	// 右值參數(shù)

在PrintAndProcess函數(shù)內(nèi)部,參數(shù)param被傳遞給process函數(shù),process函數(shù)分別對左值和右值進行了重載,傳入PrintAndProcess左值參數(shù)時希望process左值版本被調(diào)用,傳入PrintAndProcess右值參數(shù)時,process右值版本被調(diào)用。但是前面我們提過,一個右值引用的變量,其本身時一個左值,因此無論傳給PrintAndProcess函數(shù)的實參時左值還是右值,最終調(diào)用process函數(shù)都是左值版本。為了解決這個問題,我們就需要一種機制:當(dāng)傳入PrintAndProcess函數(shù)的實參是右值時,調(diào)用的時process的右值版本。這就是std::forward的使用場景:只把由右值初始化的參數(shù),轉(zhuǎn)換為右值。

那么std::forward如何知道param參數(shù)是被一個左值還是一個右值給初始化的呢?我們來看下std::forward實現(xiàn)的源碼:

template<class T>
constexpr T&& forward(std::remove_reference_t<T>& arg) noexcept{
    // forward an lvalue as either an lvalue or an rvalue
    return (static_cast<T&&>(arg));
}

template<class T>
constexpr T&& forward(std::remove_reference_t<T>&& arg) noexcept{
    static_assert(!std::is_lvalue_reference<_Tp>::value, "template argument"
        " substituting _Tp is an lvalue reference type");
    // forward an rvalue as an rvalue
    return (static_cast<T&&>(arg));
}

對于左值的轉(zhuǎn)發(fā),首先通過獲取類型type,定義args為左值引用的左值變量,然后通過static_cast<T&&>進行強制轉(zhuǎn)換,這里T&&會發(fā)生引用折疊,當(dāng)T被推導(dǎo)為左值引用時,則為T&& &,折疊為T&,當(dāng)推導(dǎo)為右值引用時,則本身為T&&,forward返回值與static_cast都為T&&。
對于右值的轉(zhuǎn)發(fā)不同于左值,只有當(dāng)類型時右值時才進行static_cast轉(zhuǎn)換,arg為右值引用的左值變量,通過cast轉(zhuǎn)換為T&&。
對應(yīng)到上述PrintAndProcess函數(shù)中我們進行分析:文章來源地址http://www.zghlxwxcb.cn/news/detail-408752.html

  • 當(dāng)PrintAndProcess(a),傳入的為左值A(chǔ)時,T被推導(dǎo)為A&,std::forward返回值和static_cast被推導(dǎo)為A& &&,折疊為A&,返回一個左值。
  • 當(dāng)PrintAndProcess(std::move(a)),傳入為右值時,T被推導(dǎo)為A,在std::forward返回值和static_cast被推導(dǎo)為T&&,返回一個右值。

std::move 和 std::forward對比

  • std::move執(zhí)行到右值的無條件轉(zhuǎn)換。就其本身而言,它沒有move任何東西。
  • std::forward只有在它的參數(shù)綁定到一個右值上的時候,它才轉(zhuǎn)換它的參數(shù)到一個右值。
  • std::move和std::forward只不過就是執(zhí)行類型轉(zhuǎn)換的兩個函數(shù);std::move沒有move任何東西,std::forward沒有轉(zhuǎn)發(fā)任何東西。在運行期,它們沒有做任何事情。它們沒有產(chǎn)生需要執(zhí)行的代碼,一個byte都沒有。
  • std::forward()不僅可以保持左值或者右值不變,同時還可以保持const、Lreference、Rreference、validate等屬性不變;

到了這里,關(guān)于MordernC++之左值(引用)與右值(引用)的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來自互聯(lián)網(wǎng)用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實不符,請點擊違法舉報進行投訴反饋,一經(jīng)查實,立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費用

相關(guān)文章

  • 【送書】【C++11】左值引用和右值引用

    【送書】【C++11】左值引用和右值引用

    需要云服務(wù)器等云產(chǎn)品來學(xué)習(xí)Linux的同學(xué)可以移步/--騰訊云--/--阿里云--/--華為云--/官網(wǎng),輕量型云服務(wù)器低至112元/年,新用戶首次下單享超低折扣。 ??? 目錄 一、新的類功能 1、新的默認(rèn)成員函數(shù) 2、類成員變量初始化 3、強制生成默認(rèn)函數(shù)的default 4、禁止生成默認(rèn)函

    2023年04月09日
    瀏覽(20)
  • 左值引用、右值引用,std::move() 的匯編解釋

    左值引用、右值引用,std::move() 的匯編解釋

    1:左值引用 引用其實還是指針,但回避了指針這個名字。由編譯器完成從地址中取值。以vs2019反匯編: 如圖,指針和引用的匯編代碼完全一樣。但引用在高級語言層面更友好,對人腦。比如可以少寫一個 * 號和 - 。 ,以下是指針和引用的使用: 以上就是左值引用,引用的

    2024年02月04日
    瀏覽(15)
  • C++ 學(xué)習(xí)系列 1 -- 左值、右值與萬能引用

    C++ 學(xué)習(xí)系列 1 -- 左值、右值與萬能引用

    簡單的說,左值可以放在等號的左邊,右值可以放在等號的右邊。 左值可以取地址,右值不能取地址。 1.1 左值舉例: 變量、函數(shù)或數(shù)據(jù)成員 返回左值引用的表達(dá)式 如 ++x、x = 1、cout \\\' \\\'? int x = 0 1.2 右值舉例: 返回非引用類型的表達(dá)式 如 x++、x + 1 除字符串字面量之外的字面

    2024年02月14日
    瀏覽(20)
  • C++右值引用(左值表達(dá)式、右值表達(dá)式)(移動語義、完美轉(zhuǎn)發(fā)(右值引用+std::forward))(有問題懸而未決)

    C++右值引用(左值表達(dá)式、右值表達(dá)式)(移動語義、完美轉(zhuǎn)發(fā)(右值引用+std::forward))(有問題懸而未決)

    在 C++ 中,表達(dá)式可以分為左值表達(dá)式和右值表達(dá)式。左值表達(dá)式指的是可以出現(xiàn)在賦值語句左邊的表達(dá)式,例如變量、數(shù)組元素、結(jié)構(gòu)體成員等;右值表達(dá)式指的是不能出現(xiàn)在賦值語句左邊的表達(dá)式,例如常量、臨時對象、函數(shù)返回值等。 右值是指將要被銷毀的臨時對象或

    2024年02月04日
    瀏覽(23)
  • C++ 左值和右值

    C++ 左值和右值

    在C++11中所有的值必屬于左值、右值兩者之一,右值又可以細(xì)分為純右值、將亡值。在C++11中可以取地址的、有名字的就是左值,反之,不能取地址的、沒有名字的就是右值(將亡值或純右值)。舉個例子,int a = b+c, a 就是左值,其有變量名為a,通過a可以獲取該變量的地址;

    2024年02月14日
    瀏覽(14)
  • C++左值右值完美轉(zhuǎn)發(fā)轉(zhuǎn)移

    英文含義: 左值(Lvalue) : Locator value ,意味著它指向一個具體的內(nèi)存位置。 右值(Rvalue) : Read value ,指的是可以讀取的數(shù)據(jù),但不一定指向一個固定的內(nèi)存位置。 定義 左值 :指的是一個持久的內(nèi)存地址。左值可以出現(xiàn)在賦值操作的左側(cè)或右側(cè)。例如,變量、數(shù)組的元

    2024年03月10日
    瀏覽(35)
  • 【C語言深入】細(xì)聊C語言中的“左值”和“右值”

    【C語言深入】細(xì)聊C語言中的“左值”和“右值”

    左值就是那些可以出現(xiàn)在賦值符號左邊的東西,它標(biāo)識了一個可以存儲結(jié)果值的地點。 程序在編譯時,編譯器會為每個變量分配一個地址(左值),這個地址在編譯是即可知。 也就是說,左值在編譯時即可知,左值標(biāo)志存儲結(jié)果的一個地方,也可以理解為左值就是一塊空間。

    2024年02月14日
    瀏覽(12)
  • C++面試八股文:什么是左值,什么是右值?

    某日二師兄參加XXX科技公司的C++工程師開發(fā)崗位第16面: 面試官:什么是左值,什么是右值? 二師兄:簡單來說,左值就是可以使用 符號取地址的值,而右值一般不可以使用 符號取地址。 二師兄:一般左值存在內(nèi)存中,而右值存在寄存器中。 二師兄:嚴(yán)格意義上分,右值

    2024年02月09日
    瀏覽(17)
  • C++:深入理解C++11新特性:Chapter3:左值和右值

    C++:深入理解C++11新特性:Chapter3:左值和右值

    在C語言中,我們常常會提起左值(lvalue),右值(rvalue)這樣的稱呼,而在編譯程序時,編譯器有時也會報出錯誤信息中包含 左值,右值說法。不過左值、右值通常不是通過一個嚴(yán)謹(jǐn)?shù)亩x而為人所知。下面我通過這樣一個例子,來引導(dǎo)大家認(rèn)識: 左值,右值,左值引用,右

    2024年02月04日
    瀏覽(102)
  • C++右值引用,右值引用與const引用的區(qū)別

    左值:可以取地址的、有名字的變量,有持久性; 右值:一般是不可尋址的常量,或在表達(dá)式求值過程中創(chuàng)建的無名臨時對象,短暫性的。 C++11新增了另一種引用——右值引用。這種引用可指向右值,使用聲明。 右值引用只能引用臨時變量和常量值。 const引用:可以引用普

    2024年01月18日
    瀏覽(26)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包