c++中的左值与右值

左值(lvalue)和右值(rvalue)是 c/c++ 中一个比较晦涩基础的概念,很多写了好久c/c++的人甚至没有听过这个名字,但这个概念到了 c++11 后却变得十分重要,它们是理解 move/forward 等新语义的基础。php

左值右值的定义

左值与右值这两概念是从 c 中传承而来的,在 c 中,左值指的是既可以出如今等号左边也能出如今等号右边的变量(或表达式),右值指的则是只能出如今等号右边的变量(或表达式).html

int a; int b; a = 3; b = 4; a = b; b = a; // 如下写法不合法。
3 = a; a+b = 4;

在 c 语言中,一般来讲有名字的变量就是左值(如上面例子中的 a, b),而由运算操做(加减乘除,函数调用返回值等)所产生的中间结果(没有名字)就是右值,如上的 3 + 4, a + b 等。咱们暂且能够认为:左值就是在程序中可以寻值的东西,右值就是无法取到它的地址的东西(不彻底准确),但如上概念到了 c++ 中,就变得稍有不一样。具体来讲,在 c++ 中,每个表达式都会产生一个左值,或者右值,相应的,该表达式也就被称做“左值表达式", "右值表达式"。对于基本数据类型来讲(primitive types),左值右值的概念和 c 没有太多不一样,不一样的地方在于自定义的类型,并且这种不一样比较容易让人混淆:c++

1) 对于基础类型,右值是不可被修改的(non-modifiable),也不可被 const, volatile 所修饰(cv-qualitification ignored)函数

2) 对于自定义的类型(user-defined types),右值却容许经过它的成员函数进行修改。this

对于 1),这和 c 是一致的,2) 倒是 C++ 中所独有, 所以,若是你看到 C++ 中以下的写法,千万不要惊讶:spa

class cs
{
    public:
        cs(int i): i_(i)  { cout << "cs(" << i <<") constructor!" << endl; }
        ~cs() { cout << "cs destructor,i(" << i_ << ")" << endl; }

        cs& operator=(const cs& other)
        {
            i_ = other.i_;
            cout << "cs operator=()" << endl;
            return *this;
        }

        int get_i() const { return i_; }
        void change(int i)  { i_ = i; }

    private:
        int i_;
};

cs get_cs()
{
    static int i = 0;
    return cs(i++);
}

int main()
{
     // 合法
    (get_cs() = cs(2)).change(323);
    get_cs() = cs(2);// operator=()
    get_cs().change(32);

    return 0;
}

这个特性看起来多少有些奇怪,由于一般来讲,自定义类型应该设计得和内置类型尽可能同样(所谓 value type,value semantic),但容许成员函数改变右值这个特性却有意无心使得自定义类型特殊化了。对此,咱们其实能够这样想,也许会好理解点:自定义类型容许有成员函数,而经过右值调用成员函数是被容许的,但成员函数有可能不是 const 类型,所以经过调用右值的成员函数,也就可能会修改了该右值,done!设计

左值引用,右值引用

关于右值,在 c++11 之前有一个十分值得关注的语言的特性:右值能被 const 类型的引用所指向,因此以下代码是合法的。c++11

const cs& ref = get_cs();

并且准确地说,右值只能被 const 类型的 reference 所指向,非 const 的引用则是非法的:code

// error  cs& ref = get_cs();

当一个右值被 const 引用指向时,它的生命周期就被延长了,这个用法我在前面一篇博客里讲到过它的相关应用。其中暗藏的逻辑其实就是:右值不能当成左值使用(但左值能够当成右值使用)。另外值得注意的是,对于前面提到的右值的两个特性:htm

1) 容许调用成员函数。

2) 只能被 const reference 指向。

它们致使了一些比较有意思的结果,好比:

void func(cs& c)
{
   cout << "c:" << c.get_i() << endl;
}

//error
func(get_cs());

//正确
func(get_cs() = get_cs());

其中: func(get_cs() = get_cs()); 可以被正常编译执行的缘由就在于,cs 的成员函数 operator=() 返回的是 cs&!不容许非 const reference 引用 rvalue 并非完美的,它事实上也引发了一些问题,好比说拷贝构造函数的接口不一致了,这是什么意思呢?

class cs
{
    public:      
        cs& operator=(const cs& c);
};

// 另外一种写法
class cs2
{
    public:      
        cs2& operator=(cs2& c);
};

上面两种写法的不一样之处就在于参数,一个是 const reference,一个是非 const。对于自定义类型的参数,一般来讲,若是函数不须要修改传进来的参数,咱们每每就按 const reference 的写法,但对于 copy constructor 来讲,它常常是须要修改参数的值,好比 auto_ptr。

// 相似auto_ptr
class auto_ptr { public: auto_ptr(auto_tr& p) { ptr_ = p.ptr_; p.ptr_ = NULL; } private: void* ptr_; };

因此,对于 auto_ptr 来讲,它的 copy constructor 的参数类型是 non const reference。有些状况下,这种写法应该被鼓励,毕竟 non const reference 比 const reference 更能灵活应对各类状况,从而保持一致的接口类型,固然也有代价,参数的语义表达不许确了。除此更大的问题是若是拷贝构造函数写成这样子,却又对 rvalue 的使用带来了极大的不变,如前面所讲的例子,rvalue 不能被 non const reference 所引用,因此像 auto_ptr 的这样的类的 copy constructor 就不能接受 rvalue.

// 错误
auto_ptr p(get_ptr());

// operator=() 同理,错误。
auto_ptr p = get_ptr();

这也是 auto_ptr 很很差用的缘由之一,为了解决这个问题,c++11 中引入了一种新的引用类型,该种引用是专门用来指向 rvalue 的,有了这种新类型,对 lvalue 和 rvalue 的引用就能明确区分开来了。由于有了这种新的类型,接着就引出了 c++11 中新的语义,move(), forward() 等,这儿先卖个关子,咱们下次再讲。

[参考]

http://accu.org/index.php/journals/227

相关文章
相关标签/搜索