移动语义

 

定义

在 C++ 11 标准之前, 若想用其它对象初始化一个同类新对象, 只能借助类中复制(拷贝)构造函数

拷贝构造函数实现原理就是为新对象复制一份和其它对象一模一样数据

当类中拥有指针类型成员变量时, 拷贝构造函数中需要以深拷贝(而非浅拷贝)方式复制该指针成员

移动语义是用于优化资源管理和提高程序性能的一种重要特性, 引入于C++11

主要通过转移(移动)资源所有权而不是复制资源, 以减少不必要深拷贝开销

移动语义主要依赖于右值引用(rvalue reference)和移动构造函数(Move Constructor)及移动赋值运算符(Move Assignment Operator)来实现

支持类型

C++中支持移动语义类型一般是具有资源所有权或需要深拷贝类型

常见支持移动语义类型有,

  • 标准库容器

如std::vector、std::string、std::unique_ptr等

容器和智能指针因为管理动态内存, 所以非常适合使用移动语义

  • 用户定义类型

若一个类管理动态内存或者其他资源(如文件句柄), 则可以为其实现移动构造和移动赋值

  • 智能指针

如std::unique_ptr, 它具有唯一资源所有权, 不能复制, 只能移动

支持移动语义类型通常会利用std::move()来显式地将对象转换为右值, 以触发移动构造函数或移动赋值运算符

实现

移动构造函数

移动构造函数用于初始化新对象时, 将资源从一个右值对象移动到新对象上

class MyClass {
public:
    // 移动构造函数
    MyClass(MyClass&& other) noexcept {
        // 将other资源转移到当前对象
        this->resource = other.resource;
        // 清空other资源
        other.resource = nullptr;
    }
};

移动赋值运算符

移动赋值运算符用于将资源从一个右值对象赋值给已有对象

class MyClass {
public:
    // 移动赋值运算符
    MyClass& operator=(MyClass&& other) noexcept {
        if (this != &other) { // 避免自我赋值
            // 释放当前对象资源
            delete this->resource;
            // 转移资源
            this->resource = other.resource;
            // 清空other资源
            other.resource = nullptr;
        }
        return *this;
    }
};

禁用拷贝构造和拷贝赋值

为了确保对象只能通过移动来转移资源, 而不是复制资源, 可以显式禁用拷贝构造函数和拷贝赋值运算符

class MyClass {
public:
    // 禁用拷贝构造
    MyClass(const MyClass&) = delete;
    // 禁用拷贝赋值
    MyClass& operator=(const MyClass&) = delete;
    // 移动构造
    MyClass(MyClass&& other) noexcept;
    // 移动赋值
    MyClass& operator=(MyClass&& other) noexcept; 
};

std::move

std::move()是C++标准库提供的一个实用函数, 可以将左值转换为右值, 以便触发移动语义

C++11 标准中借助右值引用可以为指定类添加移动构造函数, 当使用该类右值对象(可以理解为临时对象)初始化同类对象时, 编译器会优先选择移动构造函数

#include <iostream>

class MyClass {
public:
    MyClass() : mNum(new int(0)) {
        std::cout << "Construct!" << std::endl;
    }

    MyClass(const MyClass& d) : mNum(new int(*d.mNum)) {
        std::cout << "Copy Construct!" << std::endl;
    }

    MyClass(MyClass&& d) noexcept {
        mNum = d.mNum;
        d.mNum = NULL;
        std::cout << "Move Construct!" << std::endl;
    }
public:
    int* mNum;
};

int main() {
    MyClass myClass;
    MyClass demo2 = myClass;
    MyClass demo3 = std::move(myClass);
    return 0;
}

运行结果

Construct!
Copy Construct!
Move Construct!

myClass 对象作为左值, 直接用于初始化 demo2 对象, 调用拷贝构造函数

通过调用 move() 函数可以得到 myClass 对象右值形式, 用其初始化 demo3 对象, 优先调用移动构造函数