基本概念

在C++中,移动语义(Move Semantics)是为了提高性能而引入的一种机制,特别是在涉及大数据结构或需要频繁复制对象的场景中。移动语义允许“移动”资源,而不是“复制”资源,从而避免了不必要的深拷贝操作。

通常,当我们复制一个对象时,会分配新的内存并复制该对象的所有内容。而移动语义则允许我们将资源的所有权从一个对象转移到另一个对象,而不进行深拷贝。这样可以显著提高性能,尤其是在处理动态分配的大数据结构时。

右值引用(Rvalue Reference)是实现移动语义的基础。右值引用使用符号&&,它只能绑定到右值(临时对象)上,表示对象的资源可以被“移动”。

如果一个对象是右值,可以将它的资源直接“搬”到另一个对象,而不再执行复制操作。在移动之后,被移动的对象处于有效但未指定的状态,通常会释放它的资源,或设置为空状态。

示例

#include <iostream>
#include <utility> // for std::move
#include <cstring>

class MyString {
private:
    char* data; // 用于存储字符串的指针
    size_t length;

public:
    // 构造函数
    MyString(const char* str) {
        length = strlen(str);
        data = new char[length + 1];
        strcpy(data, str);
        std::cout << "Constructing MyString: " << data << std::endl;
    }

    // 拷贝构造函数 (深拷贝)
    MyString(const MyString& other) {
        length = other.length;
        data = new char[length + 1];
        strcpy(data, other.data);
        std::cout << "Copying MyString: " << data << std::endl;
    }

    // 移动构造函数 (移动语义)
    MyString(MyString&& other) noexcept {
        data = other.data;  // "偷取"资源
        length = other.length;

        // 将源对象置于有效但未指定的状态
        other.data = nullptr;
        other.length = 0;

        std::cout << "Moving MyString (constructor): " << data << std::endl;
    }

    // 移动赋值运算符
    MyString& operator=(MyString&& other) noexcept {
        std::cout << "Moving MyString (assignment): " << other.data << std::endl;

        // 自我赋值检查
        if (this != &other) {
            // 释放已有的资源
            delete[] data;

            // "偷取"资源
            data = other.data;
            length = other.length;

            // 将源对象置于有效但未指定的状态
            other.data = nullptr;
            other.length = 0;
        }
        return *this;
    }

    // 析构函数
    ~MyString() {
        if (data != nullptr) {
            std::cout << "Destroying MyString: " << data << std::endl;
            delete[] data;
        }
    }
};

int main() {
    MyString str1("Hello, World!");

    // 使用移动构造函数,在创建对象时将右值的资源转移到新对象。
    MyString str2 = std::move(str1); // 触发移动构造函数

    MyString str3("Temporary String");

    // 使用移动赋值运算符,是在对象已经存在的情况下,将右值的资源转移到已有
    str3 = std::move(str2); // 触发移动赋值运算符

    return 0;
}
  • 移动构造函数在MyString str2 = std::move(str1);这一行被调用。此时str2是一个新创建的对象,直接从str1“偷取”资源,而不需要处理任何已有的资源。
  • 移动赋值运算符在str3 = std::move(str2);这一行被调用。此时str3已经存在,移动赋值运算符首先需要释放str3之前持有的资源,然后再从str2那里“偷取”资源。