1. 程式人生 > >表示式左值右值(C++學習)

表示式左值右值(C++學習)

左值右值是表示式的屬性,該屬性稱為 value category。按該屬性分類,每一個表示式屬於下列之一:

lvalue

left value,傳統意義上的左值

xvalue

expiring value, x值,指通過“右值引用”產生的物件

prvalue

pure rvalue,純右值,傳統意義上的右值(?)

而 xvalue 和其他兩個型別分別複合,構成:

lvalue + xvalue = glvalue

general lvalue,泛左值

xvalue + prvalue = rvalue

右值

區分?

++x 與 x++假定x的定義為int x=0;,那麼前者是 lvalue,後者是rvalue。前者修改自身值,並返回自身;後者先建立一個臨時對像,為其賦值,而後修改x的值,最後返回臨時對像。

區分表示式的左右值屬性有一個簡便方法:若可對錶達式用 & 符取址,則為左值,否則為右值。比如

&obj , &*ptr , &ptr[index] , &++x

有效

&1729 , &(x + y) , &std::string("meow"), &x++

無效

對於函式呼叫,根絕返回值型別不同,可以是lvalue、xvalue、prvalue:

  • The result of calling a function whose return type is anlvalue referenceis an lvalue

  • The result of calling a function whose return type is anrvalue referenceis an xvalue.

  • The result of calling a function whose return type isnot a referenceis a prvalue.

const vs non-const

左值和右值表示式都可以是const或non-const。

比如,變數和函式的定義為:

string one("lvalue");
const string two("clvalue");
string three() { return "rvalue"; }
const string four() { return "crvalue"; }

那麼表示式:

表示式

分類

one

modifiable lvalue

two

const lvalue

three()

modifiable rvalue

four()

const rvalue

引用

Type&

只能繫結到可修改的左值表示式

const Type&

可以繫結到任何表示式

Type&&

可繫結到可修改的左值或右值表示式

const Type&&

可以繫結到任何表示式

過載函式

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

string one("lvalue");
const string two("clvalue");
string three() { return "rvalue"; }
const string four() { return "crvalue"; }

void func(string& s)
{
    cout << "func(string& s): " << s << endl;
}

void func(const string& s)
{
    cout << "func(const string& s): " << s << endl;
}

void func(string&& s)
{
    cout << "func(string&& s): " << s << endl;
}

void func(const string&& s)
{
    cout << "func(const string&& s): " << s << endl;
}

int main()
{
    func(one);
    func(two);
    func(three());
    func(four());
    return 0;
}

結果:

func(string& s): lvalue
func(const string& s): clvalue
func(string&& s): rvalue
func(const string&& s): crvalue

如果只保留const string& 和 string&& 兩個過載函式,結果為:

func(const string& s): lvalue
func(const string& s): clvalue
func(string&& s): rvalue
func(const string& s): crvalue

右值引用

C++0x第5章的第6段:

Named rvalue references are treated as lvalues and unnamed rvalue references to objects are treated as xvalues; rvalue references to functions are treated as lvalues whether named or not.
  • 具名右值引用被視為左值
  • 無名對物件的右值引用被視為x值
  • 對函式的右值引用無論具名與否都將被視為左值
#include <iostream>
#include <string>

void F1(int&& a)
{
    std::cout<<"F1(int&&) "<<a<<std::endl;
}

void F1(const int& a)
{
    std::cout<<"F1(const int&) "<<a<<std::endl;
}

void F2(int&& a)
{
    F1(a);
}

int main()
{
    int && a=1;
    F2(a);
    F1(a);
    F2(2);
    F1(2);
    return 0;
}

結果

F1(const int&) 1
F1(const int&) 1
F1(const int&) 2
F1(int&&) 2

移動語義

在這之前,如果寫一個交換兩個值的swap函式:

template <class T> swap(T& a, T& b)
{
    T tmp(a);   // now we have two copies of a
    a = b;      // now we have two copies of b
    b = tmp;    // now we have two copies of tmp (aka a)
}

之後

template <class T> swap(T& a, T& b)
{
    T tmp(std::move(a));
    a = std::move(b);   
    b = std::move(tmp);
}

std::move 接受左值或右值引數,並返回一個右值(其所作工作很簡單)

template <class T>
typename remove_reference<T>::type&&
move(T&& a)
{
    return a;
}

要是的swap真正發揮作用,需要過載:

class T
{
public:
    T(T&& );
    T& operator = (T&& );
...

模板引數型別

為了對比左值引用和右值引用,一開始誤打誤撞,寫了這樣一個函式

template <typename Type> void Swap(Type&& sb1, Type&& sb2)
{
    Type sb(sb1);
    sb1 = sb2;
    sb2 = sb;
}

然後

int main()
{
    int a=1, b=2;
    Swap(a, b);
    std::cout<<a<<" "<<b<<std::endl;
    return 0;
}

結果卻是

2 2

不用整數,換用一個自定義的型別試試看:

class A 
{
public:
    A() {
        std::cout << "Default constructor." << std::endl;
        m_p = NULL;
    }

    ~A() {
        std::cout << "Destructor." << std::endl;
        delete m_p;
    }

    explicit A(const int n) {
        std::cout << "Unary constructor." << std::endl;
        m_p = new int(n);
    }

    A(const A& other) {
        std::cout << "Copy constructor." << std::endl;
        if (other.m_p) {
            m_p = new int(*other.m_p);
        } else {
            m_p = NULL;
        }
    }

    A(A&& other) {
        std::cout << "Move constructor." << std::endl;
        m_p = other.m_p;
        other.m_p = NULL;
    }

    A& operator=(const A& other) {
        std::cout << "Copy assignment operator." << std::endl;
        if (this != &other) {
            delete m_p;
            if (other.m_p) {
                m_p = new int(*other.m_p);
            } else {
                m_p = NULL;
            }
        }
        return *this;
    }

    A& operator=(A&& other) {
        std::cout << "Move assignment operator." << std::endl;
        if (this != &other) {
            delete m_p;
            m_p = other.m_p;
            other.m_p = NULL;
        }
        return *this;
    }

    int get() const {
        return m_p ? *m_p : 0;
    }

private:
    int * m_p;
};

int main()
{
    A a(1);
    A b(2);
    Swap2(a, b);
    std::cout<<a.get()<<" "<<b.get()<<std::endl;
    return 0;
}

結果

Unary constructor.
Unary constructor.
Copy assignment operator.
Copy assignment operator.
2 2
Destructor.
Destructor.

只出現了兩個物件,那麼Swap中的臨時物件去哪兒了?

C++0x 14.8.2.1

If P is a cv-qualified type, the top level cv-qualifiers of P’s type are ignored for type deduction. If P is a reference type, the type referred to by P is used for type deduction. If P is an rvalue reference to a cv unqualified template parameter and the argument is an lvalue, the type “lvalue reference to A” is used in place of A for type deduction. 

template <class T> int f(T&&);
template <class T> int g(const T&&);
int i;
int n1 = f(i); // calls f<int&>(int&)
int n2 = f(0); // calls f<int>(int&&)
int n3 = g(i); // error: would call g<int>(const int&&), which
// would bind an rvalue reference to an lvalue

也就是前面提到的

template <typename Type> void Swap(Type&& sb1, Type&& sb2)

引數推導後

void Swap<int&>(int& sb1, int& sb1)

參考