为什么会有右值引用?(移动构造、移动赋值)

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

         目录

1、左值引用的缺陷

2、移动构造解决临时对象的深拷贝

3、拓展移动赋值 


1、左值引用的缺陷

左值引用作为函数参数传递减少了参数拷贝但是作为函数返回值并不适用于所有场景比如要返回一个临时对象。

直接返回临时对象的话此时编译器就会调用拷贝构造构造一个新的临时对象保存到main函数的栈上然后销毁原本的临时对象。如果对象中有一个大小为10000的数组深拷贝对程序运行效率影响较大

class Point
{
public:
    Point(int x, int y): _x(x), _y(y) {}
    Point(const Point& p):_x(p._x), _y(p._y)
    {
        cout << "Point(Point& p)被调用了 —— 深拷贝" << endl;
    }

    Point& operator=(const Point& p)
    {
        _x = p._x;
        _y = p._y;
    }

    Point getPos() {
        Point p(_x, _y);        // 创建临时对象
        return p;               // 返回临时对象
    }
private:
    int _x;
    int _y;
};

2、移动构造解决临时对象的深拷贝

移动构造是右值引用的应用之一其本质就是将要返回的临时对象的资源移动到其他地方。下面通过示意图和案例来了解。

(1) 移动构造的本质

移动构造也是构造函数的一种区别在于传递的参数必须是右值即表达式、函数返回值等。其本质是将右值保存的资源转移到其他临时对象。以上述 getPos 函数为例

getPos函数返回一个临时对象A临时对象A出了作用域就被销毁肯定不能直接返回所以会先调用移动构造函数返回值是右值。

移动构造会将临时对象A的资源转移到新构造的对象B中然后再销毁临时对象A这样的话就减少了深拷贝提升程序的运行效率。

 

(2) 移动构造具体实现

所谓转移资源其实就是将临时对象A和临时对象B的资源交换

  • 临时对象A传递的右值形参 p
  • 临时对象B构造的对象 *this
// 移动构造
Point(Point&& p)
{
    // cout << "Point(Point&& p) 被调用了 —— 资源转移" << endl;
    this->swap(p);
}

// 资源交换
void swap(Point& p)
{
    std::swap(_x, p._x);
    std::swap(_y, p._y);
}

(3) 移动构造测试

我们在先前的代码中加入移动构造然后使用同样的方法测试

class Point
{
public:
    Point(int x, int y): _x(x), _y(y) {}
    Point(const Point& p):_x(p._x), _y(p._y)
    {
        cout << "Point(Point& p)被调用了 —— 深拷贝" << endl;
    }

    Point& operator=(const Point& p)
    {
        _x = p._x;
        _y = p._y;
    }

    // 移动构造
    Point(Point&& p)
    {
        cout << "Point(Point&& p) 被调用了 —— 资源转移" << endl;
        swap(*this, p);
    }
    
    // 交换资源
    void swap(Point& p)
    {
        std::swap(_x, p._x);
        std::swap(_y, p._y);
    }
    Point getPos() {
        Point p(_x, _y);
        return p;
    }
private:
    int _x;
    int _y;
};

 

3、拓展移动赋值 

(1) 起因

当我们把一个对象赋给另一个对象的时候其实就是把一个对象里的内容完全拷贝到另一个对象这个时候也会发生深拷贝。

Point p(10, 20);
// Point p1 = p;        // 会被编译器优化成调用拷贝构造
Point p1(20, 30);
p1 = p;        // 赋值完毕以后打算舍弃 p
    

假如我们的目的是将 p 赋给 p1 以后舍弃对象p若对象p中包含一个大小为10000的数组这个时候的深拷贝就比较影响效率了。

(2) 移动赋值

移动赋值借用了移动构造的思路既然不需要对象p那就索性将一个对象p的资源转移到对象p1

那么我们就需要新增一个 operator=() 的重载了

Point& operator=(Point&& p)
{
    // cout << "Point& operator=(Point&& p) 被调用了 —— 移动赋值资源转移" << endl;
    this->swap(p);    // swap 函数的定义详见上面的移动构造
    return *this;
}

 

(3) 移动赋值测试

测试用的代码需要稍微改动一下移动赋值的形参必须是一个右值这里我们需要用move函数将对象p 转换成右值。其内部也是以特殊的方式直接返回传入的对象这样的话返回值就变成了一个临时对象也就是右值。

int main() {
    Point p(10, 20);
    Point p1(20, 30);
    p1 = move(p);

    return 0;
}

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6