C++新特性 右值引用 移動建構函式

來源:互聯網
上載者:User

標籤:c++

1、右值引用引入的背景

臨時對象的產生和拷貝所帶來的效率折損,一直是C++所為人詬病的問題。但是C++標準允許編譯器對於臨時對象的產生具有完全的自由度,從而發展出了Copy Elision、RVO(包括NRVO)等編譯器最佳化技術,它們可以防止某些情況下臨時對象產生和拷貝。下面簡單地介紹一下Copy Elision、RVO,對此不感興趣的可以直接跳過:

(1) Copy Elision

 Copy Elision技術是為了防止某些不必要的臨時對象產生和拷貝,例如:

struct A {    A(int) {}    A(const A &) {}};A a = 42;

理論上講,上述A a = 42;語句將分三步操作:第一步由42構造一個A類型的臨時對象,第二步以臨時對象為參數拷貝構造a,第三步析構臨時對象。如果A是一個很大的類,那麼它的臨時對象的構造和析構將造成很大的記憶體開銷。我們只需要一個對象a,為什麼不直接以42為參數直接構造a呢?Copy Elision技術正是做了這一最佳化。

【說明】:你可以在A的拷貝建構函式中加一列印語句,看有沒有調用,如果沒有被調用,那麼恭喜你,你的編譯器支援Copy Elision。但是需要說明的是:A的拷貝建構函式雖然沒有被調用,但是它的實現不能沒有存取權限,不信你將它放在private許可權裡試試,編譯器肯定會報錯。

(2) 返回值最佳化(RVO,Return Value Optimization)

 返回值最佳化技術也是為了防止某些不必要的臨時對象產生和拷貝,例如:

struct A {    A(int) {}    A(const A &) {}};A get() {return A(1);}A a = get();

理論上講,上述A a = get();語句將分別執行:首先get()函數中建立臨時對象(假設為tmp1),然後以tmp1為參數拷貝構造返回值(假設為tmp2),最後再以tmp2為參數拷貝構造a,其中還伴隨著tmp1和tmp2的析構。如果A是一個很大的類,那麼它的臨時對象的構造和析構將造成很大的記憶體開銷。返回值最佳化技術正是用來解決此問題的,它可以避免tmp1和tmp2兩個臨時對象的產生和拷貝。

【說明】: a)你可以在A的拷貝建構函式中加一列印語句,看有沒有調用,如果沒有被調用,那麼恭喜你,你的編譯器支援返回值最佳化。但是需要說明的是:A的拷貝建構函式雖然沒有被調用,但是它的實現不能沒有存取權限,不信你將它放在private許可權裡試試,編譯器肯定會報錯。

b)除了返回值最佳化,你可能還聽說過一個叫具名返回值最佳化(Named Return Value Optimization,NRVO)的最佳化技術,從程式員的角度而言,它其實跟RVO同樣的邏輯。只是它的臨時對象具有變數名標識,例如修改上述get()函數為:

A get() {    A tmp(1); // #1    // do something    return tmp;}A a = get(); // #2

想想上述修改後A類型共有幾次物件建構?雖然#1處看起來有一次顯示地構造,#2處看起來也有一次顯示地構造,但如果你的編譯器支援NRVO和Copy Elision,你會發現整個A a = get();語句的執行過程,只有一次A對象的構造。如果你在get()函數return語句前列印tmp變數的地址,在A a = get();語句後列印a的地址,你會發現兩者地址相同,這就是應用了NRVO技術的結果。

(3) Copy Elision、RVO無法避免的臨時對象的產生和拷貝

雖然Copy Elision和NVO(包括NRVO)等技術能避免一些臨時對象的產生和拷貝,但某些情況下它們卻發揮不了作用,例如:

template <typename T>void swap(T& a, T& b) {    T tmp(a);    a = b;    b = tmp;}

我們只是想交換a和b兩個對象所擁有的資料,但卻不得不使用一個臨時對象tmp備份其中一個對象,如果T類型對象擁有指向(或引用)從堆記憶體配置的資料,那麼深拷貝所帶來的記憶體開銷是可以想象的。為此,C++11標準引入了右值引用,使用它可以使臨時對象的拷貝具有move語意,從而可以使臨時對象的拷貝具有淺拷貝般的效率,這樣便可以從一定程度上解決臨時對象的深度拷貝所帶來的效率折損。

 

2、C++03標準中的左值與右值

要理解右值引用,首先得區分左值(lvalue)和右值(rvalue)。

C++03標準中將運算式分為左值和右值,並且“非左即右”:

    Every expression is either an lvalue or an rvalue.

區分一個運算式是左值還是右值,最簡便的方法就是看能不能夠對它取地址:如果能,就是左值;否則,就是右值。

【說明】:由於右值引用的引入,C++11標準中對錶達式的分類不再是“非左即右”那麼簡單,不過為了簡單地理解,我們暫時只需區分左值右值即可,C++11標準中的分類後面會有描述。

 

3、右值引用的綁定規則

右值引用(rvalue reference,&&)跟傳統意義上的引用(reference,&)很相似,為了更好地區分它們倆,傳統意義上的引用又被稱為左值引用(lvalue reference)。下面簡單地總結了左值引用和右值引用的綁定規則(函數類型對象會有所例外):

(1)非const左值引用只能綁定到非const左值;
(2)const左值引用可綁定到const左值、非const左值、const右值、非const右值;
(3)非const右值引用只能綁定到非const右值;
(4)const右值引用可綁定到const右值和非const右值。

測試例子如下:

struct A { A(){} };A lvalue;                             // 非const左值對象const A const_lvalue;                 // const左值對象A rvalue() {return A();}              // 返回一個非const右值對象const A const_rvalue() {return A();}  // 返回一個const右值對象// 規則一:非const左值引用只能綁定到非const左值A &lvalue_reference1 = lvalue;         // okA &lvalue_reference2 = const_lvalue;   // errorA &lvalue_reference3 = rvalue();       // errorA &lvalue_reference4 = const_rvalue(); // error// 規則二:const左值引用可綁定到const左值、非const左值、const右值、非const右值const A &const_lvalue_reference1 = lvalue;         // okconst A &const_lvalue_reference2 = const_lvalue;   // okconst A &const_lvalue_reference3 = rvalue();       // okconst A &const_lvalue_reference4 = const_rvalue(); // ok// 規則三:非const右值引用只能綁定到非const右值A &&rvalue_reference1 = lvalue;         // errorA &&rvalue_reference2 = const_lvalue;   // errorA &&rvalue_reference3 = rvalue();       // okA &&rvalue_reference4 = const_rvalue(); // error// 規則四:const右值引用可綁定到const右值和非const右值,不能綁定到左值const A &&const_rvalue_reference1 = lvalue;         // errorconst A &&const_rvalue_reference2 = const_lvalue;   // errorconst A &&const_rvalue_reference3 = rvalue();       // okconst A &&const_rvalue_reference4 = const_rvalue(); // ok// 規則五:函數類型例外void fun() {}typedef decltype(fun) FUN;  // typedef void FUN();FUN       &  lvalue_reference_to_fun       = fun; // okconst FUN &  const_lvalue_reference_to_fun = fun; // okFUN       && rvalue_reference_to_fun       = fun; // okconst FUN && const_rvalue_reference_to_fun = fun; // ok

【說明】:(1) 一些支援右值引用但版本較低的編譯器可能會允許右值引用綁定到左值,例如g++4.4.4就允許,但g++4.6.3就不允許了,clang++3.2也不允許,據說VS2010 beta版允許,正式版就不允許了,本人無VS2010環境,沒測試過。

(2)右值引用綁定到字面值常量同樣符合上述規則,例如:int &&rr = 123;,這裡的字面值123雖然被稱為常量,可它的類型為int,而不是const int。對此C++03標準文檔4.4.1節及其腳註中有如下說明:

    If T is a non-class type, the type of the rvalue is the cv-unqualified version of T.
    In C++ class rvalues can have cv-qualified types (because they are objects). This differs from ISO C, in which non-lvalues never have cv-qualified types.

因此123是非const右值,int &&rr = 123;語句符合上述規則三。

此,我們已經瞭解了不少右值引用的知識點了,下面給出了一個完整地利用右值引用實現move語意的例子:

#include <iostream>#include <cstring>#define PRINT(msg) do { std::cout << msg << std::endl; } while(0)template <class _Tp> struct remove_reference        {typedef _Tp type;};template <class _Tp> struct remove_reference<_Tp&>  {typedef _Tp type;};template <class _Tp> struct remove_reference<_Tp&&> {typedef _Tp type;};template <class _Tp>inline typename remove_reference<_Tp>::type&& move(_Tp&& __t) {    typedef typename remove_reference<_Tp>::type _Up;    return static_cast<_Up&&>(__t);}class A {public:    A(const char *pstr) {        PRINT("constructor");        m_data = (pstr != 0 ? strcpy(new char[strlen(pstr) + 1], pstr) : 0);    }    A(const A &a) {        PRINT("copy constructor");        m_data = (a.m_data != 0 ? strcpy(new char[strlen(a.m_data) + 1], a.m_data) : 0);    }    A &operator =(const A &a) {        PRINT("copy assigment");        if (this != &a) {            delete [] m_data;            m_data = (a.m_data != 0 ? strcpy(new char[strlen(a.m_data) + 1], a.m_data) : 0);        }        return *this;    }    A(A &&a) : m_data(a.m_data) {        PRINT("move constructor");        a.m_data = 0;    }    A & operator = (A &&a) {        PRINT("move assigment");        if (this != &a) {            m_data = a.m_data;            a.m_data = 0;        }return *this;    }    ~A() { PRINT("destructor"); delete [] m_data; }private:    char * m_data;};void swap(A &a, A &b) {    A tmp(move(a));    a = move(b);    b = move(tmp);}int main(int argc, char **argv, char **env) {    A a("123"), b("456");    swap(a, b);    return 0;}

輸出結果為:

constructorconstructormove constructormove assigmentmove assigmentdestructordestructordestructor


聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.