日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程语言 > c/c++ >内容正文

c/c++

C++——右值引用

發(fā)布時間:2024/4/17 c/c++ 33 豆豆
生活随笔 收集整理的這篇文章主要介紹了 C++——右值引用 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

文章目錄

  • 一、基本知識
    • 1.作用
    • 2.右值的基本概念以及與左值的辨別
    • 3.右值引用的特性
  • 二、右值引用優(yōu)化性能
  • 三、移動語義(move)和完美轉(zhuǎn)發(fā)(forward)
    • 1.移動語義move
    • 2.完美轉(zhuǎn)發(fā)forward


一、基本知識

1.作用

作用:C++11中引用了右值引用和移動語義,可以避免無謂的復(fù)制,提高了程序性能。

2.右值的基本概念以及與左值的辨別

①C++11中的所有的值必將屬于左值、將亡值、純右值三者之一,將亡值和純右值都屬于右值。
左值是表達式結(jié)束后仍然存在的持久對象右值是指表達式結(jié)束時就不存在的臨時對象
③區(qū)分左值和右值的便捷方法是看能不能對表達式取地址,如果能則為左值,否則為右值。
④將亡值是C++11新增的、與右值引用相關(guān)的表達式,比如:將要被移動的對象、T&&函數(shù)返回的值、std::move返回值和轉(zhuǎn)換成T&&的類型的轉(zhuǎn)換函數(shù)返回值。
左值是有標(biāo)識符、可以取地址的表達式,最常見的情況有:變量、函數(shù)或數(shù)據(jù)成員的名字返回左值引用的表達式,如 ++x、x = 1、cout << ’ ',字符串字面量如 “hello world”。
純右值是沒有標(biāo)識符、不可以取地址的表達式,一般也稱之為“臨時對象”。最常見的情況有:返回非引用類型的表達式,如 x++、x + 1、make_shared(42)、除字符串字面量之外的字面量,如 42、true。

3.右值引用的特性

①右值引用就是對一個右值進行引用的類型。因為右值沒有名字,所以我們只能通過引用的方式找到它。
②無論聲明左值引用還是右值引用都必須立即進行初始化,因為引用類型本身并不擁有所把綁定對象的內(nèi)存,只是該對象的一個別名。
③通過右值引用的聲明,該右值又“重獲新生”,其生命周期其生命周期與右值引用類型變量的生命周期一樣,只要該變量還活著,該右值臨時量將會一直存活下去。
&& 的總結(jié)如下:
(1)左值和右值是獨立于它們的類型的,右值引用類型可能是左值也可能是右值。
(2)auto&& 或函數(shù)參數(shù)類型自動推導(dǎo)的 T&& 是一個未定的引用類型,被稱為 universal references,它可能是左值引用也可能是右值引用類型,取決于初始化的值類型。
(3)所有的右值引用疊加到右值引用上仍然是一個右值引用,其他引用折疊都為左值引用。當(dāng) T&& 為模板參數(shù)時,輸入左值,它會變成左值引用,而輸入右值時則變?yōu)榫呙挠抑狄谩?br /> (4)編譯器會將已命名的右值引用視為左值,而將未命名的右值引用視為右值。

二、右值引用優(yōu)化性能

對于含有堆內(nèi)存的類,我們需要提供深拷貝的拷貝構(gòu)造函數(shù),如果使用默認構(gòu)造函數(shù),會導(dǎo)致堆內(nèi)存的重復(fù)刪除,比如下面的代碼:

#include <iostream> using namespace std; class A { public:A() :m_ptr(new int(0)) {cout << "constructor A" << endl;}~A() {cout << "destructor A, m_ptr:" << m_ptr << endl;delete m_ptr;m_ptr = nullptr;} private:int* m_ptr; };// 為了避免返回值優(yōu)化,此函數(shù)故意這樣寫 A Get(bool flag) {A a;A b;cout << "ready return" << endl;if (flag)return a;elsereturn b; }int main() {{A a = Get(false); // 運行報錯}cout << "main finish" << endl;return 0; }

在上面的代碼中,默認構(gòu)造函數(shù)是淺拷貝,main函數(shù)的 a 和Get函數(shù)的 b 會指向同一個指針 m_ptr,在析構(gòu)的時候會導(dǎo)致重復(fù)刪除該指針。解決辦法就是用深拷貝。如下深拷貝代碼:

//2-1-memory2 #include <iostream> using namespace std; class A { public:A() :m_ptr(new int(0)) {cout << "constructor A" << endl;}A(const A& a) :m_ptr(new int(*a.m_ptr)) {cout << "copy constructor A" << endl;}~A() {cout << "destructor A, m_ptr:" << m_ptr << endl;delete m_ptr;m_ptr = nullptr;} private:int* m_ptr; };// 為了避免返回值優(yōu)化,此函數(shù)故意這樣寫 A Get(bool flag) {A a;A b;cout << "ready return" << endl;if (flag)return a;elsereturn b; }int main() {{A a = Get(false); // 正確運行}cout << "main finish" << endl;return 0; }

運行結(jié)果:

這樣就可以保證拷貝構(gòu)造時的安全性,但有時這種拷貝構(gòu)造卻是不必要的,比如上面代碼中的拷貝構(gòu)造就是不必要的。上面代碼中的 Get 函數(shù)會返回臨時變量,然后通過這個臨時變量拷貝構(gòu)造了一個新的對象 b,臨時變量在拷貝構(gòu)造完成之后就銷毀了,如果堆內(nèi)存很大,那么,這個拷貝構(gòu)造的代價會很大,
帶來了額外的性能損耗。可以用右值引用來優(yōu)化:

#include <iostream> using namespace std; class A { public:A() :m_ptr(new int(0)) {cout << "constructor A" << endl;}A(const A& a) :m_ptr(new int(*a.m_ptr)) {cout << "copy constructor A" << endl;}A(A&& a) :m_ptr(a.m_ptr) {a.m_ptr = nullptr;cout << "move constructor A" << endl;}~A() {cout << "destructor A, m_ptr:" << m_ptr << endl;if (m_ptr)delete m_ptr;} private:int* m_ptr; }; // 為了避免返回值優(yōu)化,此函數(shù)故意這樣寫 A Get(bool flag) {A a;A b;cout << "ready return" << endl;if (flag)return a;elsereturn b; } int main() {{A a = Get(false); // 正確運行}cout << "main finish" << endl;return 0; }

運行結(jié)果:

三、移動語義(move)和完美轉(zhuǎn)發(fā)(forward)

1.移動語義move

移動語義是通過右值引用來匹配臨時值的,那么,普通的左值是否也能借組移動語義來優(yōu)化性能呢?C++11為了解決這個問題,提供了std::move()方法來將左值轉(zhuǎn)換為右值,從而方便應(yīng)用移動語義。move是將對象的狀態(tài)或者所有權(quán)從一個對象轉(zhuǎn)移到另一個對象,只是轉(zhuǎn)義,沒有內(nèi)存拷貝

#include <iostream> #include <vector> #include <cstdio> #include <cstdlib> #include <string> using namespace std; class MyString { private:char* m_data;size_t m_len;void copy_data(const char* s) {m_data = new char[m_len + 1];memcpy(m_data, s, m_len);m_data[m_len] = '\0';} public:MyString() {m_data = NULL;m_len = 0;}MyString(const char* p) {m_len = strlen(p);copy_data(p);}MyString(const MyString& str) {m_len = str.m_len;copy_data(str.m_data);cout << "Copy Constructor is called! source: " << str.m_data << endl;}MyString& operator=(const MyString& str) {if (this != &str) {m_len = str.m_len;copy_data(str.m_data);}cout << "Copy Assignment is called! source: " << str.m_data << endl;return *this;}// 用c++11的右值引用來定義這兩個函數(shù)MyString(MyString&& str) {cout << "Move Constructor is called! source: " << str.m_data << endl;m_len = str.m_len;m_data = str.m_data; //避免了不必要的拷貝str.m_len = 0;str.m_data = NULL;}MyString& operator=(MyString&& str) {cout << "Move Assignment is called! source: " << str.m_data << endl;if (this != &str) {m_len = str.m_len;m_data = str.m_data; //避免了不必要的拷貝str.m_len = 0;str.m_data = NULL;}return *this;}virtual ~MyString() {if (m_data)free(m_data);} };int main() {MyString a;a = MyString("Hello"); //無名對象,右值,Move AssignmentMyString b = a; // Copy ConstructorMyString c = std::move(a); // Move Constructor,將左值轉(zhuǎn)為右值return 0; }

運行結(jié)果:

2.完美轉(zhuǎn)發(fā)forward

forward 完美轉(zhuǎn)發(fā)實現(xiàn)了參數(shù)在傳遞過程中保持其值屬性的功能,即若是左值,則傳遞之后仍然是左值,若是右值,則傳遞之后仍然是右值。所謂完美轉(zhuǎn)發(fā)是指在函數(shù)模板中,完全依照模板的參數(shù)的類型保持參數(shù)的左值或者右值特性,將參數(shù)傳遞給函數(shù)模板中調(diào)用的另一個函數(shù),不管參數(shù)是T&&這種未定的引用還是明確的左值引用或者右值引用,它會按照參數(shù)本來的類型轉(zhuǎn)發(fā)。

(1)根據(jù)前面所描述的,下面這種引用類型既可以對左值引用,亦可以對右值引用。但要注意,引用以后,這個val值它本質(zhì)上是一個左值

Template<class T> void func(T &&val);

(2)注意下面的語句,a是一個右值引用,但其本身a也有內(nèi)存名字,所以a本身是一個左值,再用右值引用引用a這是不對的。

int &&a = 10; int &&b = a; //錯誤

(3)因此我們有了std::forward()完美轉(zhuǎn)發(fā),這種T &&val中的val是左值,但如果我們用std::forward (val),就會按照參數(shù)原來的類型轉(zhuǎn)發(fā)。

int &&a = 10; int &&b = std::forward<int>(a);

(4)通過示例進一步說明完美轉(zhuǎn)發(fā)

#include <iostream> using namespace std;template <class T> void Print(T& t) {cout << "L" << t << endl; }template <class T> void Print(T&& t) {cout << "R" << t << endl; }//既可以對左值引用,也可以對右值引用。但要注意,引用以后,這個t值它本質(zhì)上是一個左值 template <class T> void func(T&& t) {Print(t);//一定是左值,因為t此時已經(jīng)是一個具名的變量Print(std::move(t));//move(t)是右值Print(std::forward<T>(t));//forward(t)按照參數(shù)原來的類型轉(zhuǎn)發(fā) }int main() {cout << "-- func(1)" << endl;func(1);//右值int x = 10;int y = 20;cout << "-- func(x)" << endl;func(x); // x本身是左值cout << "-- func(std::forward<int>(y))" << endl;func(std::forward<int>(y)); //return 0; }

運行結(jié)果:

func(1) :由于1是右值,所以未定的引用類型T&&t被一個右值初始化后變成了一個右值引用,但是此時t已經(jīng)是一個具名的左值了。所以Print(1)是L1;Print(move(t)),此時t是一個左值,但是move(t)會返回值是右值屬性,所以Print(move(t))打印出來是R1;Print(forward(t)),forward會按參數(shù)原來的類型轉(zhuǎn)發(fā),這里已經(jīng)發(fā)生了類型推導(dǎo),因此,它還是一個右值,所以這里的T&&不是一個未定的引用類型,會調(diào)用void PrintT(T&&t)函數(shù)打印 “R1”。

同上:
func(x)未定的引用類型T&&t被一個左值初始化后變成了一個左值引用,因此,Print(t)是L10;Print(move(t))是R10;Print(forward(x))是L10,因為forward(t)會自動類型推到參數(shù)原本的屬性,x是左值。

func(forward(y))未定的引用類型T&&t被一個左值初始化后變成了一個左值引用,因此,Print(t)是L20;由于move(t)是無條件的將左值轉(zhuǎn)化為右值,所以同上,Print(move(t))是R20。

最讓人費解的無疑就是func(forward< int >(y)),最后func里面的Print(forward< int >(t))調(diào)用的是void Print(T&& t),打印出來R20。
先說一下引用重疊
只有右值引用與右值引用重疊才會是左值引用,而左值引用與任何引用疊加都是左值引用。如下:
T& && v 左值引用
T& & v 左值引用
T&& & v 左值引用
T&& && v 右值引用
而forward< int >(y)返回值類型為右值引用類型T&&,也就是說當(dāng)我們傳遞給func()函數(shù)的時候,是以右值引用類型傳遞進去的,模板參數(shù)為引用類型T、T&&時,返回右值引用,所以func函數(shù)里的Print(forward< int >(t))進行類型推導(dǎo)的時候,推導(dǎo)出參數(shù)原類型為右值引用類型。

優(yōu)秀博客點擊此處。

總結(jié)

以上是生活随笔為你收集整理的C++——右值引用的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。