[讀書筆記][深入應用C++11]2.1 右值引用

C++98中的左值與右值

在C++98/03中,只有左值與右值兩種類型值類型。左值是指表達式結束後依然存在的持久對象,右值是指表達式結束時就不存在的臨時對象。區分左值和右值得方法是:能不能對表達式取地址,如果能則爲左值,否則爲右值。

C與C++98中右值的區別

C++檢測到右值在內存中存在對應的實體,會自動轉換爲左值。
例如:以下代碼在C中無法編譯通過,因爲其對右值進行操作;而在C++中可以編譯通過,因爲其自動換爲左值a,對a進行操作。

int a=0;
(a=1)++;

以下代碼在C++中也編譯不過,因爲a+1是右值,且在內存中無對應的實體。

int a=0;
(a+1)++;

C++11中的左值、將亡值與純右值

在C++11中,右值由兩個概念構成:將亡值與純右值。C++11中的所有值必屬於左值、將亡值、純右值之一。

左值引用與右值引用

左值引用是:對左值的引用。如:

int number=10;
int& refnum=number;

右值引用:對右值(也可以是左值)的引用。如:

int&& refnum=10;
Object&& refnum=getObject();

通過右值引用的聲明,延長了右值得生命週期,使得其生命週期與右值引用變量的生命週期一樣。通過右值引用,對象從函數返回值返回時,不需要調用拷貝構造函數創建新對象,提高了效率。

&&的特性

未定引用類型

未定引用類型是指:未確定引用是左值引用還是右值引用,這取決於初始化的值。如果其被左值初始化,則它是左值引用;如果其被右值初始化,則它是右值引用。
只有發生自動類型推斷時(如函數模板的類型自動推導,或auto關鍵字),&&纔是未定引用類型。

void func(Object&& param);//param是右值引用

template<typename T>
void func(T&& param);//param是左值引用

auto&& Number=10;//Number是右值引用

int Num=10;
auto&& Number=Num;//Number是左值引用

&&的總結

  1. 左值和右值是獨立於它們的類型的,右值引用類型可能是左值也可能是右值。
  2. auto&&或者函數參數類型(函數模板)自動推導的T&&是一個未定的引用類型,被稱爲universal references,它可能是左值引用也可能是右值引用類型,取決於初始化的值類型。
  3. 所有的右值引用疊加到右值引用上仍然是一個右值引用,其他引用摺疊都爲左值引用。當T&&爲模板參數時,輸入左值,它變爲左值引用,而輸入右值是則變爲具名的右值引用。
  4. 編譯器會將具名的右值引用視爲左值,而將未命名的右值引用視爲右值。

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

移動構造函數

移動構造函數的參數是一個右值引用類型,它只需要淺拷貝,不需要深拷貝,從而提高了性能。右值引用的一個重要的目的就是用來支持移動語義。

    MyString(MyString&& String) {
        m_len = String.m_len;
        m_data = String.m_data;
        String.m_data = NULL;
        String.m_len = 0;
        cout << "移動構造函數!" << endl;
    }

移動語義

移動語義可以將資源通過淺拷貝的方式從一個對象轉移到另外一個對象,這樣能夠減少不必要的臨時對象的創建、拷貝以及銷燬,可以大幅度提高C++應用程序的性能,消除臨時對象的維護對性能的影響。下面是移動語義的例子:

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

class MyString {
private:
    char* m_data;
    size_t m_len;
    void copy_data(const char *str) {
        m_data = new char[m_len + 1];
        memcpy(m_data, str, m_len);
        m_data[m_len] = '\0';
    }
public:
    MyString() {
        m_data = NULL;
        m_len = 0;
        cout << "構造函數!" << endl;
    }
    MyString(const char* str) {
        m_len = strlen(str);
        copy_data(str);
        cout << "構造函數!" << endl;
    }
    MyString(const MyString& String) {
        m_len = String.m_len;
        copy_data(String.m_data);
        cout << "拷貝構造函數!" << endl;
    }
    MyString(MyString&& String) {
        m_len = String.m_len;
        m_data = String.m_data;
        String.m_data = NULL;
        String.m_len = 0;
        cout << "移動構造函數!" << endl;
    }
    MyString& operator=(const MyString& String) {
        if (this != &String) {
            m_len = String.m_len;
            copy_data(String.m_data);
        }
        cout << "賦值運算符!" << endl;
        return *this;
    }

    MyString& operator=(MyString&& String) {
        if (this != &String) {
            m_len = String.m_len;
            m_data = String.m_data;
            String.m_data = NULL;
            String.m_len = 0;
        }
        cout << "移動賦值運算符!" << endl;
        return *this;
    }

    virtual ~MyString() {
        if (m_data != NULL) {
            free(m_data);
            m_data = NULL;
            m_len = 0;
        }
        cout << "析構函數!" << endl;
    }
};
int main(){
    vector<MyString> Vector;
    Vector.push_back(MyString("Hello"));
    return 0;
}

需要注意的是:在提供移動構造函數的同時也要提供拷貝構造函數,以防止移動不成功的時候還能使用拷貝構造函數。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章