C++ 11 中的右值引用

來源:互聯網
上載者:User

右值引用的功能

首先,我並不介紹什麼是右值引用,而是以一個例子裡來介紹一下右值引用的功能:

    #include <iostream>
    #include
<vector>
    using
namespace std;

    class obj
    {
    public :
        obj() { cout << ">> create obj " << endl; }
        obj(const obj& other) { cout << ">> copy create obj " << endl; }
    };

    vector<obj> foo()
    {
        vector<obj> c;
        c.push_back(obj());

        cout << "---- exit foo ----" << endl;
        return c;
    }

    int main()
    {
        vector<obj> k;
        k = foo();
    }

首先我們編譯一下這個函數,運行結果如下:

    tianfang > g++ main.cpp
    tianfang > a.out
    >> create obj
    >> copy create obj
    ---- exit foo ----
    >> copy create obj
    tianfang >

可以看到,對obj對象執行了兩次構造。vector是一個常用的容器了,我們可以很容易的分析這這兩次拷貝構造的時機:

  1. foo函數第二行,調用push_back的時候,會在vector裡建立一個obj的副本
  2. main函數第二行,執行複製函數的時候,會把foo()返回的對象全部複製過來,再次執行一次拷貝構造

由於對象的拷貝構造的開銷是非常大的,因此我們想就可能避免他們。其中,第一次拷貝構造是vector的特性所決定的,不可避免。但第二次拷貝構造,在C++ 11中就是可以避免的了。

    tianfang > g++ -std=c++11 main.cpp
    tianfang > a.out
    >> create obj
    >> copy create obj
    ---- exit foo ----
    tianfang >

可以看到,我們除了加上了一個-std=c++11選項外,什麼都沒幹,但現在就把第二次的拷貝構造給去掉了。它是如何?這一過程的呢?

在老版本中,當我們執行第二行的賦值操作的時候,執行過程如下:

  1. foo()函數返回一個臨時對象(這裡用~tmp來標識它)
  2. 執行vector的 '=' 函數,將對象k中的現有成員刪除,將~tmp的成員複製到k中來
  3. 刪除臨時對象~tmp

在C++11的版本中,執行過程如下:

  1. foo()函數返回一個臨時對象(這裡用~tmp來標識它)
  2. 執行vector的 '=' 函數,將對象k中的成員~tmp的成員互換,此時k中的成員就被替換成了~tmp中的成員。
  3. 刪除臨時對象~tmp(此時就刪除了以前的k中的成員)

關鍵的過程就是第2步,它不是複製而是交換,從而避免的成員的拷貝,但效果卻是一樣的。不用修改代碼,效能卻得到了提升,對於程式員來說就是一份免費的午餐。

但是,這份免費的午餐也不是無條件就可以擷取的,帶上-std=c++11編譯時間,如果使用STL代碼可以享用這份午餐,但如果使用我們以前的老代碼發現還是和以前的功能是一樣的,那麼,如何讓我們以前的代碼也能得到這個效率的提升呢?

 
 

通過交換減少資料的拷貝

為了示範如何在我們的代碼中也擷取這個效能提升,首先我先寫了一個山寨的vector:

    #include
<iostream>
    #include
<vector>
    using
namespace std;

    class obj
    {
    public :
        obj() { cout << ">> create obj " << endl; }
        obj(const obj& other) { cout << ">> copy create obj " << endl; }
    };

    template <class T>
    class container
    {
    public:
        T* value;

    public:
        container() : value(NULL) {};
        ~container() { delete value; }

        container(const container& other)
        {
            value = new T(*other.value);
        }

        const container& operator = (const container& other)
        {
            delete value;
            value = new T(*other.value);
            return *this;
        }

        void push_back(const T& item)
        {
            delete value;
            value = new T(item);
        }
    };

    container<obj> foo()
    {
        container<obj> c;
        c.push_back(obj());

        cout << "---- exit foo ----" << endl;
        return c;
    }

    int main()
    {
        container<obj> k ;
        k = foo();    
    }

這個vector只能容納一個元素,但並不妨礙我們的示範,其功能和前面的例子是一樣的,運行這段代碼,結果如下:

    tianfang > make
    g++ -std=c++11 main.cpp
    tianfang > a.out
    >> create obj
    >> copy create obj
    ---- exit foo ----
    >> copy create obj
    tianfang >

如前所述,仍然有兩次拷貝構造。其實前面已經說過交換實現減少拷貝構造的原理,那麼,我們可以通過修改 '=' 函數來手動實現這一過程。

    const
container& operator = (container& other)
    {
        T* tmp = value;
        value = other.value;
        other.value = tmp;
        return *this;
    }

在VC中運行這段代碼,發現運行結果和預期一致,

    >> create obj
    >> copy create obj
    ---- exit foo ----

但是,gcc中卻無法通過編譯,原因很簡單:gcc期望的賦值函數的參數是const型的,而這裡為了交換成員,而不能使用const型。

那麼,雖然gcc中不能生效,是否可以說在vc中就可以以這種形式擷取效能提升呢?答案是否定的。雖然在這段代碼中這麼寫沒有問題,但賦值函數本身是期望複製功能的,而不是交換。例如,修改後下面的運行結果就不對了。

    int main()
    {
        container<obj> k, k2;
        k = foo();    

        //預期結果是複製,但執行了交換
        k2 = k;
    }

gcc的警示是有道理的:如果 '=' 函數實現的是複製功能,雖然效率低點,但保證了功能正確,但如果實現的是交換的功能,則不能保證功能一定正確。只有當 '=' 函數右邊的對象為一個臨時變數的時候,由於臨時變數會馬上被刪除掉,此時的交換和複製的效果是一樣的。其實VC也應該把這個警示加上才合適。

PS:對臨時變數定義和來源不清楚的朋友可以參考一下這篇文章。

現在的問題是:我們無法在賦值函數裡區分傳入的是一個臨時對象還是非臨時對象,因此只能執行複製操作。為瞭解決這一問題,c++中引入了一個新的賦值函數的重載形式:

    container& operator = (container&& other)

這個賦值函數通常稱為移動賦值函數,和老版本的相比,它有兩點區別:

  1. 入參不是const型,因此它是可以更改入參的值的,從而實現交換操作
  2. 入參前面有兩個&號,這個是C++11引入的新文法,稱為右值引用,它的使用方式和普通引用是一樣的,唯一的區別是可以指向臨時變數。

現在,我們就有兩個版本的賦值函數了,C++11在文法層級也做了適應:

  • 如果入參是臨時變數,則執行移動賦值函數,如果沒有定義移動賦值函數,則執行複製賦值函數(以保證老版本代碼能編譯通過)
  • 如果入參不是臨時變數,則執行普通的複製賦值函數

現在,我們實現一下山寨版的移動賦值函數:

    container& operator = (container&& other)
    {
        delete value;
        value = other.value;
        other.value = NULL;
        return *this;

    }

運行後結果就和我們期望的那樣,避免了成員的第二次的拷貝構造。

和移動賦值函數相應的,也有一個一個移動建構函式,也最好實現以下:

    container (container&& other)
    {
        value = other.value;
        other.value = NULL;
    }

我們也可以實現自己的右值引用版的重載函數,這裡就不多介紹了。

注意:本文所示的代碼只是為了示範和實現右值引用,力求簡潔,並沒有寫得很完善(一個典型的缺失就是在賦值函數中沒有判斷入參是否是本身),請不要將其應用於項目中。

完善的版本請看MSDN文章:如何編寫一個移動建構函式,其相應的對右值引用的介紹文章Rvalue引用聲明:&&也非常值得一讀。

 

通過std::move函數顯式使用交換

首先看一下這段代碼:

    class
bigobj
    {
    public :
        bigobj() { cout << ">> create obj " << endl; }
        bigobj(const
bigobj& other) { cout << ">> copy create obj " << endl; }
        bigobj(bigobj&& other) { cout << ">> move create obj " << endl; }
    };

    int main()
    {
        list<bigobj> list;
        for(int i = 0; i < 3; i++)
        {
            bigobj obj;
            list.push_back(obj);
        }
    }

啟動並執行時候就會發現:雖然我們定義了移動建構函式,但是它仍然會執行拷貝建構函式。這是因為編譯器並不認為obj是臨時變數。關於什麼變數才是臨時變數,前文已經給了個連結來說明它,簡單的說,我們能夠看到的命名變數都不是臨時變數。

雖然obj對象不是語言層級的臨時變數,但是從功能上來看,它就是一個臨時變數,是可以使用移動建構函式來消除拷貝帶來的效能損失的。為瞭解決這一問題,C++提供了一個move函數來把obj變數強制轉換為右值引用,這樣就可以使用移動建構函式了。

    for(int i = 0; i < 3; i++)
    {
        bigobj obj;
        list.push_back(std::move(obj));
    }

不過,需要注意的是,和系統識別的臨時變數而自動使用右值引用不同,這種強制轉換是有一定的風險的,由於在push_back後執行了交換操作,如果再次使用它會出現非預期的結果,只有能確定該變數不會再次被使用才能執行這種轉換。

 

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在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.