(原創)C++11改進我們的程序之右值引用

http://www.cnblogs.com/qicosmos/p/3369940.html

本次主要講c++11中的右值引用,后面還會講到右值引用如何結合std::move優化我們的程序。

c++11增加了一個新的類型,稱作右值引用(R-value reference),標記為T &&,說到右值引用類型之前先要了解什么是左值和右值。
左值具名,對應指定內存域,可訪問;右值不具名,不對應內存域,不可訪問。臨時對像是右值。左值可處于等號左邊,右值只能放在等號右邊。區分表達式的左右值屬性有一個簡便方法:若可對表達式用 & 符取址,則為左值,否則為右值。
1.簡單的賦值語句
如:int i = 0;
在這條語句中,i 是左值,0 是臨時值,就是右值。在下面的代碼中,i 可以被引用,0 就不可以了。立即數都是右值。
2.右值也可以出現在賦值表達式的左邊,但是不能作為賦值的對象,因為右值只在當前語句有效,賦值沒有意義。
如:((i>0) ? i : j) = 1;
在這個例子中,0 作為右值出現在了”=”的左邊。但是賦值對象是 i 或者 j,都是左值。
在 C++11 之前,右值是不能被引用的,最大限度就是用常量引用綁定一個右值,如 :
const int &a = 1;
在這種情況下,右值不能被修改的。但是實際上右值是可以被修改的,既然右值可以被修改,那么就可以實現右值引用。右值引用能夠方便地解決實際工程中的問題。

int && a = 1; //&&為右值引用

&&的特性

  實際上T&&并不是一定表示右值引用,它的引用類型是未定的,即可能是左值有可能是右值。看看這個例子:

復制代碼
template<typename T>
void f(T&& param);f(10); //10是右值
int x = 10;
f(x); //x是左值
復制代碼

  從這個例子可以看出,param有時是左值引用,有時是右值引用,它在上面的例子中&&實際上是一個未定的引用類型。這個未定的引用類型被scott meyers稱為universal references(可以認為它是種通用的引用類型),它必須被初始化,它是左值應用還是右值引用取決于它的初始化,如果&&被一個左值初始化的話,它就是一個左值引用;如果它被一個右值初始化的話,它就是一個右值引用。

&&為universal references時的唯一條件是有類型推斷發生。

復制代碼
template<typename T>
void f(T&& param); //這里T的類型需要推導,所以&&是一個universal references

template<typename T>
class Test {
...
Test(Test&& rhs); // 已經定義了一個特定的類型, 沒有類型推斷
... // && 是一個右值引用
};void f(Test&& param); // 已經定義了一個確定的類型, 沒有類型推斷,&& 是一個右值引用
復制代碼

再看一個復雜一點的例子

template<typename T>
void f(std::vector<T>&& param); 

這里既有推斷類型T又有確定類型vector,那么這個param到底是什么類型呢?
它是右值引用類型,因為在調用這個函數之前,這個vector<T>中的推斷類型已經確定了,所以到調用f時沒有類型推斷了。

再看看這個例子:

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

這個param是universal references嗎?錯,它是右值引用類型,也許會迷糊,T不是推斷類型嗎,怎么會是右值引用類型。其實還有一條規則:universal references僅僅在T&&下發生,任何一點附加條件都會使之失效,而變成一個右值引用。

引用折疊(Reference collapsing)規則:

  1. 所有的右值引用疊加到右值引用上變成一個右值引用
  2. 所有的其它引用類型疊加都變成一個左值引用
  3. 左值或者右值是獨立于它的類型的,也就是說一個右值引用類型的左值是合法的。
int&& var1 = x; // var1 is of type int&& (no use of auto here)
auto&& var2 = var1; // var2 is of type int& ,var2的類型是universal references(有類型推導)

var1的類型是一個左值類型,但var1本身是一個左值;
var1是一個左值,根據引用折疊規則,var2是一個int&

?

int w1, w2;
auto&& v1 = w1;
decltype(w1)&& v2 = w2; 

v1是一個universal reference,它被一個左值初始化,所以它最終一個左值;
v2是一個右值引用類型,但它被一個左值初始化,一個左值初始化一個右值引用類型是不合法的,所以會編譯報錯。但是如果我希望把一個左值賦給一個右值引用類型該怎么做呢 ,用std::move,decltype(w1)&& v2 = std::move(w2); std::move可以將一個左值轉換成右值,關于std::move將在下一篇博文中介紹。

&&的總結:

  1. 左值和右值是獨立于它們的類型的,一個左值的類型有可能是右值引用類型。
  2. T&&是一個未定的引用類型,它可能是左值引用也可能是右值引用類型,取決于初始化的值類型。
  3. &&成為未定的引用類型的唯一條件是:T&&且發生類型推斷。
  4. 所有的右值引用疊加到右值引用上變成一個右值引用,其它引用折疊都為左值引用。

如果想更詳細了解&&,可以參考scott-meyers這個文章:http://isocpp.org/blog/2012/11/universal-references-in-c11-scott-meyers

右值引用優化性能,避免深拷貝

右值引用是用來支持轉移語義的。轉移語義可以將資源 ( 堆,系統對象等 ) 從一個對象轉移到另一個對象,這樣能夠減少不必要的臨時對象的創建、拷貝以及銷毀,能夠大幅度提高 C++ 應用程序的性能。消除了臨時對象的維護 ( 創建和銷毀 ) 對性能的影響。

以一個簡單的 string 類為示例,實現拷貝構造函數和拷貝賦值操作符。

復制代碼
 class MyString { private: char* m_data; size_t   m_len; void copy_data(const char *s) { m_data = new char[m_len+1]; memcpy(_data, s, m_len); m_data[_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); std::cout << "Copy Constructor is called! source: " << str.m_data << std::endl; } MyString& operator=(const MyString& str) { if (this != &str) { m_len = str.m_len; copy_data(str._data); } std::cout << "Copy Assignment is called! source: " << str.m_data << std::endl; return *this; } virtual ~MyString() { if (m_data) free(m_data); } }; 

void test() { MyString a; a = MyString("Hello"); std::vector<MyString> vec; vec.push_back(MyString("World")); }
復制代碼

實現了調用拷貝構造函數的操作和拷貝賦值操作符的操作。MyString(“Hello”) 和 MyString(“World”) 都是臨時對象,也就是右值。雖然它們是臨時的,但程序仍然調用了拷貝構造和拷貝賦值,造成了沒有意義的資源申請和釋放的操作。如果能夠直接使用臨時對象已經申請的資源,既能節省資源,有能節省資源申請和釋放的時間。這正是定義轉移語義的目的。

用c++11的右值引用來定義這兩個函數

復制代碼
MyString(MyString&& str) { std::cout << "Move Constructor is called! source: " << str._data << std::endl; _len = str._len; _data = str._data; //避免了不必要的拷貝str._len = 0; str._data = NULL; }
復制代碼
復制代碼
MyString& operator=(MyString&& str) { std::cout << "Move Assignment is called! source: " << str._data << std::endl; if (this != &str) { _len = str._len; _data = str._data; //避免了不必要的拷貝str._len = 0; str._data = NULL; } return *this; }
復制代碼

有了右值引用和轉移語義,我們在設計和實現類時,對于需要動態申請大量資源的類,應該設計右值引用的拷貝構造函數和賦值函數,以提高應用程序的效率。

c++11 boost技術交流群:296561497,歡迎大家來交流技術。

一點夢想:盡自己一份力,讓c++的世界變得更美好!

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/383864.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/383864.shtml
英文地址,請注明出處:http://en.pswp.cn/news/383864.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

(原創)C++11改進我們的程序之move和完美轉發

http://www.cnblogs.com/qicosmos/p/3376241.html 本次要講的是右值引用相關的幾個函數&#xff1a;std::move, std::forward和成員的emplace_back&#xff0c;通過這些函數我們可以避免不必要的拷貝&#xff0c;提高程序性能。move是將對象的狀態或者所有權從一個對象轉移到另…

微型個人博客服務器

Http相關簡介 Http是應用層的基于請求響應的一個協議, 其中Http的請求響應可以分為四部分. 請求行, 請求報頭,空行, 請求正文.其中請求行包括了請求方法, url, 版本號, 請求報頭包括請求屬性, 冒分割的鍵值對, 每組屬性之間都以換行的形式分開, 最后一空行作為請求的結束標識.…

HDU6428-Calculate-數論函數

并不知道為什么同樣一份代碼早上超時下午就A了…好像數據是隨機的? 做的第一道不是簡單板題的數論函數題.果然做不出來… 在網上研究了好久,才算稍微研究明白.看到了兩種推導的思路.(寫了半天發現講起來好麻煩,有時間再來更新) #include<cstdio> #include<cstring&g…

[C/C++]關于C++11中的std::move和std::forward

http://blog.sina.com.cn/s/blog_53b7ddf00101p5t0.htmlstd::move是一個用于提示優化的函數&#xff0c;過去的c98中&#xff0c;由于無法將作為右值的臨時變量從左值當中區別出來&#xff0c;所以程序運行時有大量臨時變量白白的創建后又立刻銷毀&#xff0c;其中又尤其是返回…

BZOJ3930-莫比烏斯反演+杜教篩

題目的意思很簡單&#xff0c;求給定區間內的gcdk的個數&#xff0c;這應該是傳統的莫比烏斯反演了。 有兩種思路&#xff0c;一種是直接將里面變成gcd1&#xff0c;然后里面看作元函數用莫比烏斯函數和恒等函數展開&#xff0c;然后改變求和順序。 還有一種是構造兩個函數&…

HDU1999不可摸數-暴力打表

看到這約數和第一反應是約數和函數&#xff0c;然后仔細一看不是正經的約數和函數&#xff0c;就去推去了&#xff0c;然后推的有點小復雜。&#xff08;數論函數那部分做多了&#xff09; 然后觀察也沒有用到什么數論部分的特殊知識啊&#xff0c;難不成真的要暴力&#xff1f…

BZOJ2818-莫比烏斯反演/歐拉函數

這道題之前沒有看數論函數的時候搞懂了,想到直接用歐拉函數做,現在再來看第一個想法就是這不是莫比烏斯反演嘛. 但還是能用簡單數論知識直接做出來的還是盡量做簡單一點. 兩種方法想到后都寫的差不多對了,都爆long long 了.萬惡的long long .實在是煩.切記切記,只要是乘積,或…

epoll用法整理 實現回聲服務端

http://blog.csdn.net/chenxun_2010/article/details/504934811、epoll是什么&#xff1f; epoll是當前在Linux下開發大規模并發網絡程序的熱門人選&#xff0c;epoll 在Linux2.6內核中正式引入&#xff0c;和select相似&#xff0c;都是I/O多路復用(IO multiplexing)技術。 Li…

HDU3430-擴展中國剩余定理

剛開始一直把題意看錯了。。。體測完智商急劇下降 正確理解題意以后自己寫一直wa&#xff0c;而且并不知道是哪里的問題&#xff0c;在網上看了一下其他人寫的改了改自己的就過了&#xff0c;可是之前的還是不知道為什么不對。 題意大概就是有一個置換群&#xff0c;問運算多…

linux shell編程多線程和wait命令學習

http://blog.csdn.net/shuanghujushi/article/details/38186303最近在使用shell做一些部署工作&#xff0c;在使用過程中&#xff0c;效率一直不高。想提高效率&#xff0c;經過分析發現&#xff0c;并不是所有操作都是需要串行的&#xff0c;一些操作是可以進行并行操作的。經…

#ifndef的作用

#ifndef是一條預編譯指令&#xff0c;就是說實在編譯的時候就會運行的指令。這個指令的作用很簡單&#xff0c;就是字面意思&#xff0c;如果沒有定義的話&#xff0c;但是卻經常使用。 因為使用這個可以避免一個源文件中兩次兩次包含同一個文件&#xff0c;或者一個工程文件中…

C++中結構體和類的區別和聯系

最主要的不同點就是結構體的訪問權限為public而且不能改變&#xff0c;而類的訪問權限可以改變&#xff0c;public的類和結構體基本一樣。 繼承上同樣表現出這樣的特點&#xff0c;struct是public繼承的&#xff0c;而class是private繼承的&#xff0c;繼承的子類的訪問權限取…

poll函數實現多路復用

http://blog.csdn.net/xluren/article/details/8206371 結構體pollfd struct pollfd { int fd; //file descriptor short event; //event of interest on fd short reven; //event that occurred on fd } 每一個pollfd結構體指定了一個被監視的文件描述符&…

抽象類(純虛函數、虛函數)和虛基類(虛繼承)

C多態 C的多態包括靜態多態和動態多態&#xff0c;靜態多態包括函數重載和泛型編程&#xff0c;動態多態包括虛函數。靜態多態實在編譯期間就能確定&#xff0c;動態多態實直在程序運行時才能確定。 抽象類 虛函數 在默認情況下對函數成員調用實施的是靜態連編&#xff0c;…

socket通信之最簡單的socket通信

http://blog.csdn.net/xluren/article/details/8043484#t15 套接字有三種類型 流式套接字&#xff08;SOCK_STREAM&#xff09;&#xff0c;數據報套接字&#xff08;SOCK_DGRAM&#xff09;及原始套接字。 1.流式套接字提供面向連接、可靠的數據傳輸服務&#xff0c;數據按字節…

Java環境配置

自己安裝的時候按照一般的安裝方法先配置了JDK的環境&#xff0c;能夠成功顯示java版本后我在安裝eclipse的時候一直提示錯誤&#xff1a; Unfortunately the Java version needed to run Eclipse Installer couldn’t be found on your system. You need the following versio…

Linux I/O復用之select函數詳解

http://blog.csdn.net/y396397735/article/details/55004775 select函數的功能和調用順序 使用select函數時統一監視多個文件描述符的&#xff1a; 1、 是否存在套接字接收數據&#xff1f; 2、 無需阻塞傳輸數據的套接字有哪些? 3、 哪些套接字發生了異常&#xff1f; sel…

【Java學習筆記一】類和對象

面向對象程序設計的一個一個重要特點是&#xff1a;封裝性。 這里的封裝性有兩方面含義&#xff1a;一是將有關的數據和操作代碼封裝在一個對象中形成一個基本單位&#xff0c;各個對象之間相互獨立互不干擾&#xff0c;二是將對象中某些部分對外隱蔽&#xff0c;即隱蔽其內部細…

深入研究socket編程(3)——使用select函數編寫客戶端和服務器

http://blog.csdn.net/chenxun_2010/article/details/50488394 首先看原先《UNIX網絡編程——并發服務器&#xff08;TCP&#xff09;》的代碼&#xff0c;服務器代碼serv.c&#xff1a; [cpp] view plaincopy #include<stdio.h> #include<sys/types.h> #inclu…

Java簡單輸入輸出

不同于面向過程中有直接的輸入輸出函數&#xff0c;Java中的輸入輸出只能通過類來實現。 比較常見的一種是使用Scanner類 需要引入java.util包&#xff0c;即在文件開始加上語句import java.util.*;創建Scanner類對象&#xff0c;屬于標準輸入流。 例如Scanner snew Scanner(S…