1. 程式人生 > >右值引用與移動建構函式的一點理解

右值引用與移動建構函式的一點理解

說明:右值引用是c++11中的新特性,本來c++中是有一個左值引用的,引入右值引用後,多了很多概念,再看prime的時候,就覺得似乎讓c++更繁瑣了。偶然在知乎上看到這個話題,於是有了一點理解,遂記錄於此。知乎連結

大象與冰箱

我們還是從大象與冰箱的故事說起。大象裝入冰箱是一個很麻煩的過程,因為大象很大,假設可以裝入冰箱。那麼如何把大象裝入另外一個冰箱。
c++有2種方案:

  1. 我們先利用量子技術分析冰箱A中大象的構造資訊,然後在冰箱B利用量子技術利用碳,氫,氧、氮、……等元素合稱,如果你願意還可以利用量子技術將冰箱A中的大象瞬間湮滅。
  2. 我們直接把冰箱A砸掉,然後再原地套上一個冰箱B(假設冰箱可以拼裝),顯然冰箱A不存在了。
    上圖:
    方案一
    方案一

    方案二:
    這裡寫圖片描述

程式碼角度分析

先看一段程式碼:
我們定義一個int形陣列類vector,資料成員包括陣列的長度size,以及陣列的首地址pdata,還有一些相關的函式。

#include<iostream>
#include<cstring>
using namespace std;
//類的定義
class vector
{
public:
    vector() :size(0), pdata(nullptr) {}//預設建構函式
    vector(size_t len, int value) : size(len)//帶引數的建構函式
    {
        pdata = new
int[len]; memset(pdata, value, sizeof(int)*len); } vector(const vector&);//拷貝建構函式 vector(vector&& v);//移動建構函式 void print()//列印陣列 { for (size_t i = 0; i < size; i++) cout << *(pdata + i) << " "; } ~vector()//解構函式 { cout
<< "deconstruction function called!" << endl; } private: size_t size; //陣列長度 int *pdata; //首地址 }; vector::vector(const vector& v) { size = v.size; delete[] pdata; pdata = new int[size]; memcpy(pdata, v.pdata, sizeof(int)*size); cout << "copy construct fuction called!" << endl; } vector::vector(vector&& v) :size(v.size), pdata(v.pdata) { v.pdata = nullptr; v.size = 0; cout << "Move construct function called!" << endl; } int main() { vector v1(500000, 0); //v1.print(); vector v2(v1); //v1.print(); //v2.print(); return 0; }

程式碼很簡單,一個類,主要看移動建構函式和拷貝建構函式,為了體現區別,我們特意在構造的時候申請500000個的大小。
執行上面的程式碼,會呼叫拷貝建構函式,雖然我們也編寫了移動建構函式,但是,處於函式匹配的原則,v2(v1)中的引數v1是左值,所以會採用拷貝建構函式,拷貝建構函式呼叫後,原物件v1依然存在。
為了呼叫移動建構函式,我們需要傳遞一個與原型匹配的右值引數,而標準庫中提供了一個左值轉右值的函式std::move(),所以我們將剛才的語句重寫為:

vector v2(move(v1));

那麼就會呼叫移動建構函式。

好處是什麼呢?

我們可以回想之前大象裝冰箱的例子,方案一就是對於拷貝建構函式,我們進行深拷貝,獲得的是大象的構造資訊,然後自己造一個大象,這個過程有時候會很麻煩,因為實際使用中,我們需要拷貝的也許不是一頭大象,而是整個動物園。然而在C++11以前,基本上都是這麼幹的。小孩子都知道,需要一個玩具車,不是先去分析玩具車,然後造出來,而是直接把喜歡的那個買回來,也就是商店的那個玩具車直接變到我家裡來。同樣,移動建構函式也是類似,假如我不需要原來的資料儲存,我只需要這個資料,為什麼一定要構造一個副本,而不是直接拿過來呢?這樣豈不是會省事很多?

也就是說,移動建構函式是反客為主,直接掌管原資料的所有權,並將原來的擁有者銷燬掉。這樣避免資料的複製,所以效率會提高。
分兩次執行上面的程式碼,我們利用vs自帶的效能分析工具來分析一下:
首先是拷貝建構函式:
這裡寫圖片描述

然後是移動建構函式:
這裡寫圖片描述

我們只需要關注main下面的資料,對比分析發現在拷貝建構函式中,主要是拷貝建構函式和解構函式,因為是深拷貝,所以需要釋放v1,v2所包含的記憶體空間,而且在呼叫拷貝建構函式時也需要開闢一塊較大的記憶體空間。而在移動建構函式版本中,主要是建構函式和移動建構函式操作,外部程式碼所佔比例相對於拷貝建構函式來說有所上升,說明移動建構函式較為高效,所需cpu資源較少。

關於效能分析,可能解釋不一定準確。若有錯誤,歡迎指出。