您身边的软件定制专家--9年开发经验为您护航

18678812288
0531-88887250

c++中的左值与右值

文章作者:济南软件开发 时间:2016年12月20日

左值(lvalue),右值(rvalue)是一个比较晦涩的概念,有些人可能甚至没有听过,但这个概念到了c++11后,却变得十分重要,它们是理解move(),forward()等新语义的基础。

 

那什么是左值右值呢?

 

左值与右值这两概念是从c中传承而来的,在c中,左值指的是能够出现在等号左边及右边的变量(表达式), 右值指的是只能出现在等号右边的变量(表达式).

 

复制代码

int a;

int b;

 

a = 3;

b = 4;

a = b;

b = a;

 

//不合法。

 

3 = a;

a+b = 4;

复制代码

通常来说,有名字的变量就是左值(如上面例子中的a, b),而由运算(加减乘除,函数调用返回值等)产生的中间结果(没有名字)就是右值,如上的3+4, a + b等。

 

我们暂且可以认为:左值就是在程序中能够寻值的东西,右值就是没法取到它的地址的东西(不完全准确)。

 

如上概念到了c++中,就变得稍有不同。

 

在c++中,每一个表达式都会产生一个左值,或者右值,相应的,该表达式也就被称作“左值表达式”,“右值表达式”。对于内置的数据类型来说(build-in types),左值右值的概念和c的没有太多不同,不同的地方在于自定义的类型。

 

而且这种不同比较容易让人混淆:

 

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

 

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

 

对于1),这是和c是一致的,2)却是c++中所独有, 因此,如果你看到c++中如下的写法,也许有会些惊讶:

 

复制代码

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;

}

复制代码

这个特性多少有些奇怪,通常来说,c++中的自定义类型是应该设计地尽量和内置类型一样才对的,但这个特性却偏偏违背了这个原则。

 

对于这个特性,我们其实可以这样想,也许会好理解点:自定义类型允许有成员函数,而通过右值调用成员函数是被允许的,但成员函数有可能不是const类型,因此通过调用右值的成员函数,也就可能会修改了该右值,done!

 

 

 

关于右值,还有一个需要注意的地方是:右值能被const类型的引用所指向

 

const cs& ref = get_cs();

而且只能被const 类型的reference所指向:

 

//error 

 

cs& ref = get_cs();

当一个右值被const reference指向时,它的生命周期就被延长了,这个用法我在前面一篇博客里讲到过它的相关应用,点这。

 

这里暗藏逻辑其实就是:右值不能直接转化成左值(但左值可以转化为右值).

 

 

 

上面提到的这两个特性:

 

 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的,有了这种新类型,在c++11中,lvalue 和rvalue的引用类型从此区分了开来,而在之前,它们是一样的。

 


想要了解更多详情欢迎来电咨询18678812288
登陆网址:www.jnydkj.cn。
联系人:王经理。