c++11的右值引用和移动语义(Move semantics)

最近接触了c++很多的概念,什么拷贝构造函数、移动构造函数、左值、右值、左值引用、右值引用等等,学习的过程好像渡劫,网上的很多博客要么就是介绍的很模糊,要么就是知识点不全。不过好在读过了这篇文章之后

https://www.cprogramming.com/c++11/rvalue-references-and-move-semantics-in-c++11.html

对于这些概念有了一个较为清晰的了解。下面就记录一下自己的学习历程

首先介绍一个很低效的c++程序:

#include <iostream>
 
using namespace std;
 
vector<int> doubleValues (const vector<int>& v)
{
    vector<int> new_values;
    new_values.reserve(v.size());
    for (auto itr = v.begin(), end_itr = v.end(); itr != end_itr; ++itr )
    {
        new_values.push_back( 2 * *itr );
    }
    return new_values;
}
 
int main()
{
    vector<int> v;
    for ( int i = 0; i < 100; i++ )
    {
        v.push_back( i );
    }
    v = doubleValues( v );
}

程序把一个vector里面的所有元素都扩大一倍。

但是这个程序非常低效,首先,在函数中建立一个新的vector:new_values。接下来,会有两次拷贝:第一次拷贝是new_values的所有成员拷贝给临时的vector对象,返回这个临时的vector对象。第二次拷贝是这个临时的vector对象的值拷贝给v,相当于又要遍历一遍,有可能v还要重新分配内存空间。第一次拷贝有可能会经过编译器优化而不进行,但是第二次拷贝是不可避免的。如果我们要进行优化的话,有没有可能是这样子呢?就是临时的vector对象中,指向临时vector对象成员的指针赋值给v,这样的话,v的指针就指向了这些临时vector对象的成员,这些成员就变成了v的成员,相当于临时vector对象的成员直接移动到了v那里,就避免了临时对象的成员拷贝一份给v,提升了整个程序的效率!

c++11 的右值引用和move就是干这件事的,减少不必要的拷贝。

 

首先先简要说一下左值和右值的区别:

上面那篇文章里对于左值的定义是在内存中有一个半永久的位置(provides a (semi)permanent piece of memory),一个左值可以通过&获取地址。而右值不可以,例如我们定义了一个类A,A()就是一个右值。再举个例子:

string getName ()
{
    return "Alex";
}

string name = getName();

上面的程序,首先通过"Alex"构造了一个临时的string对象返回,这个临时的string对象再把自己的字符成员拷贝给name对象(赋值)。name对象的赋值来源于一个临时对象,getName()是一个右值。

一般的string &a这种属于左值引用,绑定一个左值。

而string&& a属于右值引用,绑定一个右值(一般是临时对象)。

 

接下来介绍一下,基于左值的拷贝构造函数和基于右值的移动构造函数:

在c++11 之前,程序一般是这种写法:

class ArrayWrapper
{
    public:
        ArrayWrapper (int n)
            : _p_vals( new int[ n ] )
            , _size( n )
        {}
        // copy constructor
        ArrayWrapper (const ArrayWrapper& other)
            : _p_vals( new int[ other._size  ] )
            , _size( other._size )
        {
            for ( int i = 0; i < _size; ++i )
            {
                _p_vals[ i ] = other._p_vals[ i ];
            }
        }
        ~ArrayWrapper ()
        {
            delete [] _p_vals;
        }
    private:
    int *_p_vals;
    int _size;
};

当我通过一个ArrayWrapper对象构造一个新的ArrayWrapper对象的时候,会调用拷贝构造函数,新的ArrayWrapper对象的_p_vals指向一个数组,随后,other的_p_vals所指数组的所有元素拷贝给新的ArrayWrapper对象的_p_vals数组,从而初始化了新建的ArrayWrapper对象。这样其实是非常耗时的操作,我们有了这样的想法,如果我们之后不再使用other对象,我们可不可以把other的_p_vals数组移动给新建的ArrayWrapper对象?

所以我们有了接下来的移动构造函数:

class ArrayWrapper
{
public:
    // default constructor produces a moderately sized array
    ArrayWrapper ()
        : _p_vals( new int[ 64 ] )
        , _size( 64 )
    {}
 
    ArrayWrapper (int n)
        : _p_vals( new int[ n ] )
        , _size( n )
    {}
 
    // move constructor
    ArrayWrapper (ArrayWrapper&& other)
        : _p_vals( other._p_vals  )
        , _size( other._size )
    {
        other._p_vals = NULL;
        other._size = 0;
    }
 
    // copy constructor
    ArrayWrapper (const ArrayWrapper& other)
        : _p_vals( new int[ other._size  ] )
        , _size( other._size )
    {
        for ( int i = 0; i < _size; ++i )
        {
            _p_vals[ i ] = other._p_vals[ i ];
        }
    }
    ~ArrayWrapper ()
    {
        delete [] _p_vals;
    }
 
private:
    int *_p_vals;
    int _size;
};

当我们用一个临时的ArrayWrapper对象(右值)去构造一个新的ArrayWrapper对象的时候,会调用移动构造函数,other是临时的ArrayWrapper对象。我们我们让新对象的_p_vals指向other的数组,接着把other的_p_vals变成NULL,这就好像是other的成员移动到了新建的ArrayWrapper成员,避免了拷贝的开销。之后临时对象也不会在有用,直接销毁就好。可以说移动构造函数让成员移动到了新建的对象那里,避免了大的开销。

ps:拷贝构造和移动构造让我想起了大一的时候刚学C语言的时候,老师说函数形参尽量不要写结构体而要写结构体指针,我觉得二者有相似的地方。

但是需要注意的是,在移动构造函数中,other绑定了一个临时对象(右值),但是other是一个左值,&other是合法的,编译没问题的。

C++11中的move关键字功能就是把一个左值转换成一个右值,调用移动构造函数,move的实现用到了static_cast,这里不做过多讨论。我们只需要知道move关键字实现左值向右值转移。例如这样一段程序:

        vector<string> v;
	string s="abc";
	v.push_back(s);
	cout<<"s="<<s<<endl;
	
	vector<string> v1;
	string s1="abc";
	v1.push_back(move(s1));
	cout<<"s1="<<s1<<endl;

上半部分程序调用拷贝构造函数,把s的字符拷贝给新的string对象,push到v中,下半部分程序把s1转换成一个右值,调用移动构造函数,s1的字符移动到了新的string对象,所以再输出s1,s1没有字符了。

目前STL中的对象都支持移动构造和移动赋值运算符,避免了大的开销,所以开头的那个vector的例子,可以使用移动赋值运算府把临时的vector对象的成员移动到v那里。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值