右值引用与移动构造函数的一点理解

说明:右值引用是c++11中的新特性,本来c++中是有一个左值引用的,引入右值引用后,多了很多概念,再看prime的时候,就觉得似乎让c++更繁琐了。偶然在知乎上看到这个话题,于是有了一点理解,遂记录于此。知乎链接


大象与冰箱

我们还是从大象与冰箱的故事说起。大象装入冰箱是一个很麻烦的过程,因为大象很大,假设可以装入冰箱。那么如何把大象装入另外一个冰箱。
c++有2种方案:

  1. 我们先利用量子技术分析冰箱A中大象的构造信息,然后在冰箱B利用量子技术利用碳,氢,氧、氮、……等元素合称,如果你愿意还可以利用量子技术将冰箱A中的大象瞬间湮灭。
  2. 我们直接把冰箱A砸掉,然后再原地套上一个冰箱B(假设冰箱可以拼装),显然冰箱A不存在了。
    上图:
    方案一
    方案一
    方案二:
    这里写图片描述

代码角度分析

先看一段代码:
我们定义一个int形数组类vector,数据成员包括数组的长度size,以及数组的首地址pdata,还有一些相关的函数。

#include<iostream>
#include<cstring>
using namespace std;
//类的定义
class vector
{
public:
    vector() :size(0), pdata(nullptr) {}//默认构造函数
    vector(size_t len, int value) : size(len)//带参数的构造函数
    {
        pdata = new int[len];
        memset(pdata, value, sizeof(int)*len);
    }
    vector(const vector&);//拷贝构造函数
    vector(vector&& v);//移动构造函数
    void print()//打印数组
    {
        for (size_t i = 0; i < size; i++)
            cout << *(pdata + i) << "  ";
    }
    ~vector()//析构函数
    {
        cout << "deconstruction function called!" << endl;
    }
private:
    size_t size;    //数组长度
    int *pdata;   //首地址
};

vector::vector(const vector& v)
{
    size = v.size;
    delete[] pdata;
    pdata = new int[size];
    memcpy(pdata, v.pdata, sizeof(int)*size);
    cout << "copy construct fuction called!" << endl;
}

vector::vector(vector&& v) :size(v.size), pdata(v.pdata)
{
    v.pdata = nullptr;
    v.size = 0;
    cout << "Move construct function called!" << endl;
}

int main()
{
    vector  v1(500000, 0);
    //v1.print();
    vector v2(v1);
    //v1.print(); 
    //v2.print();
    return 0;
}

代码很简单,一个类,主要看移动构造函数和拷贝构造函数,为了体现区别,我们特意在构造的时候申请500000个的大小。
运行上面的代码,会调用拷贝构造函数,虽然我们也编写了移动构造函数,但是,处于函数匹配的原则,v2(v1)中的参数v1是左值,所以会采用拷贝构造函数,拷贝构造函数调用后,原对象v1依然存在。
为了调用移动构造函数,我们需要传递一个与原型匹配的右值参数,而标准库中提供了一个左值转右值的函数std::move(),所以我们将刚才的语句重写为:

vector v2(move(v1));

那么就会调用移动构造函数。


好处是什么呢?

我们可以回想之前大象装冰箱的例子,方案一就是对于拷贝构造函数,我们进行深拷贝,获得的是大象的构造信息,然后自己造一个大象,这个过程有时候会很麻烦,因为实际使用中,我们需要拷贝的也许不是一头大象,而是整个动物园。然而在C++11以前,基本上都是这么干的。小孩子都知道,需要一个玩具车,不是先去分析玩具车,然后造出来,而是直接把喜欢的那个买回来,也就是商店的那个玩具车直接变到我家里来。同样,移动构造函数也是类似,假如我不需要原来的数据保存,我只需要这个数据,为什么一定要构造一个副本,而不是直接拿过来呢?这样岂不是会省事很多?

也就是说,移动构造函数是反客为主,直接掌管原数据的所有权,并将原来的拥有者销毁掉。这样避免数据的复制,所以效率会提高。
分两次运行上面的代码,我们利用vs自带的性能分析工具来分析一下:
首先是拷贝构造函数:
这里写图片描述


然后是移动构造函数:
这里写图片描述

我们只需要关注main下面的数据,对比分析发现在拷贝构造函数中,主要是拷贝构造函数和析构函数,因为是深拷贝,所以需要释放v1,v2所包含的内存空间,而且在调用拷贝构造函数时也需要开辟一块较大的内存空间。而在移动构造函数版本中,主要是构造函数和移动构造函数操作,外部代码所占比例相对于拷贝构造函数来说有所上升,说明移动构造函数较为高效,所需cpu资源较少。

关于性能分析,可能解释不一定准确。若有错误,欢迎指出。
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
移动构造函数是 C++11 中引入的一种特殊的构造函数,它允许将一个对象的资源所有权从一个对象转移到另一个对象,而不需要进行任何资源的复制或分配。在字符串类(如 std::string)中,移动构造函数用于在对象之间高效地转移字符串的内容。 移动构造函数通常使用右值引用作为参数,并通过将原始对象的指针或索引复制到新对象中,从而避免对所有数据进行复制。这使得移动操作比复制操作更高效,特别是对于大型对象或动态分配的资源。 以下是一个示例,展示了如何实现一个移动构造函数: ```cpp class String { public: // 默认构造函数 String() : data(nullptr), size(0) {} // 移动构造函数 String(String&& other) noexcept : data(other.data), size(other.size) { other.data = nullptr; other.size = 0; } // 其他成员函数构造函数... private: char* data; // 字符串数据 int size; // 字符串长度 }; ``` 在上面的示例中,移动构造函数接受一个右值引用 `String&&` 参数。它将原始对象的指针和长度复制到新对象中,并将原始对象的指针设置为 `nullptr`,以确保在析构时不会重复释放资源。 使用移动构造函数时,可以通过将对象作为右值进行初始化或赋值来进行移动操作。例如: ```cpp String source; // 对 source 进行初始化或赋值操作... String destination = std::move(source); // 调用移动构造函数 ``` 在上面的示例中,`std::move()` 函数用于将 `source` 对象转换为右值,从而触发移动构造函数的调用。 需要注意的是,移动构造函数在对象转移后,原始对象的状态会变为有效但未指定的状态,即不能对其进行使用,除非进行重新初始化或赋值操作。 希望这个解答能够帮助你理解移动构造函数的概念和用法。如果还有其他问题,请随时提问!
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值