第13課 右值引用

一. 左值和右值ios

(一)概述編程

  1. 左值是通常指表達式結束後依然存在的持久化對象。右值指表達式結束時就再也不存在的臨時對象。便捷的判斷方法能對錶達式取地址、有名字的對象爲左值。反之,不能取地址、匿名的對象爲右值函數

  2. C++ 表達式(運算符帶上其操做數、字面量、變量名等)可按照兩種獨立的屬性類型和值類別 (value category)。而表達式的值類別必屬於左值、純右值或將亡值三者之一。如int&& x;當x用於表達式時,其類型爲右值引用,但值類別爲左值(由於有名字)spa

(二)右值的分類code

  1. 純右值(prvalue):用於識別臨時變量和一些不與對象關聯的值。函數返回值爲非引用類型、表達式臨時值(如1+3)、lambda表達式等。對象

  2. 將亡值(xvalue):是與右值引用相關的表達式一般指將要被移動的對象。如,函數返回類型爲T&&、std::move的返回值、轉換爲T&&的類型轉換函數的返回值(注意,這些都是與右值引用相關的表達式)或臨時對象。blog

(三)表達式值類別的典型例子內存

 

左值表達式(lvalue)ci

右值表達式(rvalue)字符串

純右值表達式(prvalue)

將亡值表達式(xvalue)

名字

由變量、函數或數據成員等名字構成的表達仍爲左值表達式(注意,不論其類型)。具名的右值引用爲左值表達式

匿名對象。如匿名的右值引用

函數調用或者重載運算符

返回類型是左值引用的

②返回類型是到函數的右值引用。

返回類型是非引用類型

返回類型爲對象的右值引用,例如 std::move(x)

類型轉換表達式

轉換爲左值引用類型如static_cast<int&>(x)

②轉換爲函數的右值引用類型的轉型表達式,如 static_cast<void (&&)(int)>(x)也是左值表達式

轉換爲非引用類型的轉型表達式如 static_cast<double>(x)、(int)42等。

轉換爲對象的右值引用類型的轉型表達式,例如 static_cast<char&&>(x)

對象成員表達式(a.m)

①m爲靜態成員函數

a 爲左值且 m 爲非引用類型的非靜態數據成員

①m爲普通成員函數(只能用於函數調用,如a.m(10),不能用於初始化引用或做爲函數實參等其它用途

②a 爲prvalue且 m 爲非引用類型的非靜態數據成員

a 是xvalue且 m 是非引用類型的非靜態數據成員

字面量

字符串字面量(如」hello world!」

除字符串字面量以外的字面量。如4二、true、nullptr

其它

①內置賦值表達式(如a = b、a+= b等;

間接尋址表達式(如*p)

①內置算術、邏輯和比較表達式

②內置取地址表達式(如&a)

lambda表達式

臨時對象的表達式

2、左值引用和右值引用

(一)概述

  1. 左值引用和右值引用都屬於引用類型。不管是聲明一個左值引用仍是右值引用都必須當即進行初始化。

  2. 左值引用都是左值。但具名的右值引用是左值,而匿名的右值引用是右值

(二)可綁定的值類型(設T是個具體類型)

  1. 左值引用(T&)只能綁定到左值(很是量左值)

  2. 右值引用(T&&)只能綁定到右值(很是量右值)

  3. 常量左值引用(const T&):常量左值引用是個「萬能」的引用類型它既能夠綁定到左值也能夠綁定到右值。它像右值引用同樣能夠延長右值的生命期。不過相比於右值引用所引用的右值,常量左值引用的右值在它的「餘生」中只能是隻讀的。

  4. 常量右值引用(const T&&):可綁定到右值或常量右值。因爲移動語義須要右值能夠被修改,所以常量右值引用沒有實際用處。若是須要引用右值且讓其不可更改,則常量左值引用就足夠了。

【編程實驗】左值引用和右值引用

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

class Widget
{
public:
    int x;
    int& rx = x;

    int arr[10];

    static int staticfunc(int x)
    {
        cout << "static int Widget::staticfunc(int x): " <<x << endl;
        return x;
    }

    int commonfunc(int x)
    {
        cout << "int Widget::commonfunc(int): " << x << endl;
        return x;
    }
};

Widget makeWidgetR()
{
    return Widget();
}

Widget& makeWidgetL()
{
    static Widget w; 
    return w;   //ok, Widget&是個引用類型,要注意不能返回局部對象。
}

Widget&& makeWidgetX()
{
    static Widget w;
    return std::move(w);  //ok。但要注意,Widget&&是個引用類型不能返回局部對象。
}

//返回到函數的引用類型
using RetFunc = int(int);
int demoImpl(int i)
{
    cout << "int demo(int): " << i << endl;
    return i;
}

RetFunc&& RetFuncDemo()
{
    return demoImpl;
}

int main()
{
    //1. 常見的左/右值表達式分析

    //1.1 函數形參爲左值
    //int test(int&& x){return x;} //形參x爲左值(具名變量),儘管其爲右值引用類型。

    int i = 0;
    //1.2 前置/後置自增、自減表達式
    int&& ri = i++;  //i++爲右值,表達式返回的是i的拷貝,匿名對象是個右值
    int& li = ++i;   //++i返回i自己,是個具名對象,爲左值。

    int& r2 = ri;    //雖然ri的類型是int&&,但ri自己是個具名變量。所以仍爲左值。
    //int&& r3 = ri; //error,ri是個左值
    r2 = 5;
    cout << "ri = " << ri << ", i = "<< i << endl;  //5, 2

    //1.3 解引用和取地址運算表達式
    int* p = &i;
    int& lp = *p;   //解引用:*p爲左值,由於能夠對*p取址址&(*p)。或*p = 5;
    int*&& rp = &i; //取地址:&i是個內存地址,是個右值。能夠用來初始化右值引用
    *rp = 10;
    cout  <<"i = "<< i << endl; //i = 10,i的值經過rp引用修改。

    //1.4 字面量
    const char(&hw)[13] = "hello world!"; //字符串字面量是左值,能夠用於初始化左值引用
    cout << "const char(&hw)[13] = " << hw << endl;

    int&& ten = 10;  //10爲純右值

    //1.5 賦值表達式 和 算術表達式、比較表達式、邏輯表達式
    int& a = (i += 2);  //i +=2爲賦值表達式,結果爲左值。相似的,還有 a = b、a %= b
    int&& b = i + 2;    //i+2爲算術表達式,結果爲右值。相似的還有a + b、a % b、a & b、a << b
    int&& c = (a > b);  //比較表達式結果爲右值。相似的還有:a < b、a == b、a >= b
    int&& d = (a && b); //邏輯表達式結果爲右值。相似的還有:a && b、a || b、!a

    //1.6 lambda表達式
    //auto& lam = [](int x, int y) {return x + y; };//lambda表達式爲純右值,不能綁定到左值

    //2.下標表達式
    int arr[10]; 
    int& ra1 = arr[2]; //[]下標表達式返回左值引用,還是左值
    vector<int> vec{ 1,2,3,4 };
    int& rv = vec[2];  //operator[]返回左值引用,是個左值表達式。

    //3.對象訪問表達式
    Widget w1;
    int& rx1 = w1.x;         //w1爲左值,因此w1.x爲左值
    int&& rx2 = Widget().x;  //Widget()是個臨時對象(右值)。所以,Widget().x爲右值。
    //int& rx3 = Widget().x; //error,理由同上。
    
    using WidgetStaticFunc = int(int);
    WidgetStaticFunc& wsf = w1.staticfunc; //靜態成員函數,是個左值。
    wsf(10);
    w1.commonfunc(2);   //w1.commomfunc是個純右值,只能用於函數調用,不能作其它用途。
    
    //4. 類型轉換表達式
    int&& i1 = std::move(i);              //std::move()返回值爲右值引用類型,是個右值。
    int&& i2 = static_cast<int&&>(i);     //轉換爲右值引用類型,表達式結果是個右值
    double&& d1 = static_cast<double>(i); //轉換爲右值類型,結果是個右值
    int& i3 = static_cast<int&>(i);       //轉換爲左值引用類型,表達式結果爲左值

    //5.函數返回型類型
    Widget&  w2 = makeWidgetL();   //返回左值引用類型,爲左值表達式
    Widget&& w3 = makeWidgetR();   //返回非引用類型,爲右值表達式
    Widget&& w4 = makeWidgetX();   //返回右值引用類型,爲右值表達式
    RetFunc& rf1 = RetFuncDemo();  //RetFuncDemo返回一個到函數的右值引用,是個左值表達式(C++11的標準行爲)。
    RetFunc&& rf2 = RetFuncDemo(); //RetFuncDemo仍能夠用來初始化右值引用!
    rf1(5);

    return 0;
}
/*輸出結果
ri = 5, i = 2
i = 10
const char(&hw)[13] = hello world!
static int Widget::staticfunc(int x): 10
int Widget::commonfunc(int): 2
int demo(int): 5
*/

3、萬能引用(universal reference)

(一)T&&的含義

  1. 當T是一個具體的類型時,T&&表示右值引用,只能綁定到右值。

  2. 當涉及T類型推導時T&&爲萬能引用。若用右值初始化萬能引用,則T&&爲右值引用。若用左值初始化萬能引用,則T&&爲左值引用。但無論哪一種狀況,T&&都是一種引用類型

(二)萬能引用

    1. T&&萬能引用的兩個條件

    (1)必須涉及類型推導

    (2)聲明的形式也必須正好形如「T&&」。而且該形式被限定死了,任何對其修飾都將剝奪T&&成爲萬能引用的資格。

    2. 萬能引用使用的場景

    (1)函數模板形參

      (2)auto&&

【編程實驗】萬能引用

#include <iostream>
#include <vector>

using namespace std;

class Widget {};

void func1(Widget&& param) {};  //param爲右值引用類型(不涉及類型推導)

template<typename T>
void func2(T&& param){} //param爲萬能引用(涉及類型推導)


template<typename T>
void func3(std::vector<T>&& param) {} //param爲右值引用,由於形式不是正好T&&
                                      //param的類型己肯定爲vector類型,而推導的是其元素的類型,
                                      //而不是param自己的類型。

template<typename T>
void func4(const T&& param){}  //param是個右值引用,由於被const修飾,其類型爲const T&&,而不符」正好是T&&」的要求

template<class T>
class MyVector
{
public:
    void push_back(T&& x){} //x爲右值引用。由於當定義一個MyVector對象後,T己肯定。當調用該函數時T的類型不用再推導!
                            //如MyVector<Widget> v; v.push_back(...);時T己經是肯定的Widget類型,無須再推導。
    template<class...Args>
    void emplace_back(Args&& ... args) {}; //args爲萬能引用,由於Args獨立於T的類型,當調用該函數時,需推導Args的類型。
};


int main()
{
    //1. 模板函數形參(T&&)
    Widget w;
    func2(w); //func2(T&& param),param爲Widget&(左值引用)
    func2(std::move(w)); //param爲Widget&&,是個右值引用。

    //2. auto&&
    int x = 0;
    Widget&& var1 = Widget();  //var1爲右值引用(不涉及類型推導)
    auto&& var2 = var1;        //萬能引用,auto&&被推導爲Widget&&(右值引用)
    auto&& var3 = x;           //萬能引用,被推導爲int&;(左值引用)      

    //3. 計算任意函數的執行時間:auto&&用於lambda表達式形參(C++14)
    auto timefunc = [](auto && func, auto && ... params)
    {
        //計時器啓動

        //調用func(param...)函數
        std::forward<decltype(func)>(func)(           //根據func的左右值特性來調用相應的重載&或&&版本的成員函數
            std::forward<decltype(params)>(params)... //保持參數的左/右值特性
            );
        
        //計時器中止並記錄流逝的時間
    };

    timefunc(func1, std::move(w)); //計算func1函數的執行時間

    return 0;
}
相關文章
相關標籤/搜索