移动构造函数

移动构造和移动赋值能有效的提高效率,主要用于处理右值

一.基础

在讲移动构造和移动赋值之前,我们需要先了解简单的左值与右值的区别

左值:

  • 可放在等号左边
  • 能够对地址进行操作,我们可以对其取地址
  • 持久性:左值引用一个持续存在的对象,它的生命周期超出了单个表达式,比如变量、数组的元素等

右值

  • 只能放在等号右边
  • 不能对其内存进行操作,不能进行取地址
  • 短暂性:右值通常表示临时的、短暂存在的值,比如临时对象、字面量、表达式计算的结果,函数表达式的返回值等,很快就会被释放

二、移动构造和移动赋值的使用场景

  • 临时对象的处理: 当处理临时对象(例如函数返回的对象)时,移动构造可以避免复制临时对象的开销
  • 对象交换: 在需要交换两个对象的资源时,移动构造和移动赋值可以简化代码并提高效率。
  • 优化赋值操作: 当一个对象被赋值为即将销毁的另一个对象的值时,使用移动赋值代替复制赋值可以提高效率。

移动构造和移动赋值的主要使用场景就是用于处理临时变量,如函数返回值

三、为什么移动构造和移动赋值能有效地提高运行效率

我们以一下一个例子解释

#include <iostream>
#include <cstring> 

using namespace std;

class Test {
public:
    Test() : str(nullptr){ std::cout << "Test()默认构造函数" << endl; }
    Test(const char* str_) {
        std::cout << "Test()带参构造函数" << endl;
        if (str_) {
            str = new char[strlen(str_) + 1];
            strcpy_s(str, strlen(str_) + 1, str_);
        }
        else {
            str = nullptr;
        }
    }

    Test(const Test& test) {
        std::cout << "Test()拷贝构造函数" << endl;
        if (test.str) {
            str = new char[strlen(test.str) + 1];
            strcpy_s(str,strlen(test.str)+1, test.str);
        }
        else {
            str = nullptr;
        }
    }

    Test(Test&& test) : str(test.str) {
        std::cout << "Test()移动拷贝构造函数" << endl;
        test.str = nullptr;
    }

    Test& operator=(const Test& test) {
        std::cout << "Test()赋值构造函数" << endl;
        if (this != &test) {
            delete[] str;
            if (test.str) {
                str = new char[strlen(test.str) + 1];
                strcpy_s(str, strlen(test.str) + 1, test.str);
            }
            else {
                str = nullptr;
            }
        }
        return *this;
    }

    Test& operator=(Test&& test) {
        std::cout << "Test()移动赋值构造函数" << endl;
        if (this != &test) {
            delete[] str;
            str = test.str;
            test.str = nullptr;
        }
        return *this;
    }

    ~Test() { 
        std::cout << "Test()析构函数" << endl;
        delete[] str;
    }
    char* getStr() {
        return str;
    }
private:
    char* str;
};

Test makeTest() {
    Test test("nihao");
    return test;
}

int main() {
   
    Test test1 = makeTest();
    cout << test1.getStr() << endl;
    return 0;
}

输出:
Test()带参构造函数
nihao
Test()析构函数

但是我们发现并没有调用移动赋值构造函数,这就很奇怪了,后面网上查了一下是编译器优化的原因,原因如下:

  • 当函数返回一个临时对象时,RVO 允许编译器构造这个临时对象直接在接收对象的内存位置。 这意味着函数内部构造的对象不需要通过移动构造函数或拷贝构造函数来复制到函数外的对象。编译器优化掉了临时对象的创建和销毁,减少了不必要的构造函数和析构函数的调用。

但是我们还是需要知道移动构造函数提高的原因,毕竟还有其他场合需要用到,我们先来看看如果没有移动构造函数时,这个赋值过程是怎么样的

  1. 先对返回值进行默认构造
  2. 通过赋值构造函数传递为test1

这个过程一共new了两次,如果结构体内存很大,那么就显得很浪费时间,而我们来看看移动构造和赋值是怎么处理的,它们是通过改变指针的指向进行赋值的,将原来的指针置空,很巧妙的避开了多次new的操作,减少了时间,也正是因为他们是直接修改指针的指向,所以我们一般都是用于处理临时变量。

我们知道move函数能将一个变量变成一个右值,忽略他的地址属性,那么我们就可以使用move来配合移动语义来解决一些拷贝问题,如:

#include <iostream>
#include <utility> // 包含 std::move
#include <vector>

class MyClass {
public:
    MyClass(std::vector<int>&& vec) : movableMember(std::move(vec)) {
     
    }

private:
    std::vector<int> movableMember;
};

int main() {
    std::vector<int> vec = {1, 2, 3, 4, 5};
    MyClass obj(std::move(vec)); // 创建对象并移动 vec 到 obj 内部
    return 0;
}

我们使用move函数,显式的提醒编译器调用相应的移动构造函数来转移资源,这样能有效的减少时间。

四、 注意

对某变量使用移动语义以后,该变量就处于未定义的状态,所以我们一般处理临时变量,如果不是临时变量,同时,对某个变量使用移动语义后,通常不建议再使用这个变量,因为这与移动语义的设计意图相冲突。移动语义的核心思想是“资源的转移”:将一个对象的资源(如内存、文件句柄等)转移到另一个对象,同时使原对象处于一个安全但未定义的状态,所以对变量使用移动语义后,应避免继续使用该变量,除非已对其进行了明确的重新赋值或重新初始化。move函数也是一样的到了,对某变量使用move函数以后,我们不应该再继续使用。

五、总结:

简单来说,移动语义就是通过改变指针的指向来转移资源,减少了多次new的操作以达到提高效率的效果

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值