Menu
CoddyTech

移动构造函数

本课属于 Coddy C++ 学习旅程的“面向对象编程”单元。第 21 课,共 104 课。

移动构造函数会从临时对象转移资源,而不是复制资源。拷贝构造函数会复制数据,而移动构造函数会“窃取”资源,使源对象处于有效但为空的状态。这样可以避免代价高昂的深拷贝,因为源对象反正即将被销毁。

移动构造函数接受一个右值引用,用 && 表示:

class Buffer {
    int* data;
    size_t size;
public:
    Buffer(size_t s) : size(s), data(new int[s]) {}
    
    // 移动构造函数
    Buffer(Buffer&& other) noexcept {
        data = other.data;       // 接管指针
        size = other.size;
        other.data = nullptr;    // 让源对象保持有效状态
        other.size = 0;
    }
    
    ~Buffer() { delete[] data; }
};

与复制的主要区别是:我们不会分配新内存。我们只是接管现有内存的所有权,并将源对象的指针设为 nullptr,这样它的析构函数就不会删除我们的数据。

从临时对象初始化或使用 std::move() 时,会调用移动构造函数:

Buffer createBuffer() {
    return Buffer(1000);    // 使用了移动构造函数(返回值)
}

Buffer b1(1000);
Buffer b2(std::move(b1));   // 显式移动:b1 现在为空

尽可能将移动构造函数标记为 noexcept。这样可以告诉编译器该操作不会抛出异常,从而能够在 std::vector 这样的容器中进行重要优化。

challenge icon

挑战

简单

让我们构建一个数据包系统,演示移动构造函数如何高效地转移资源所有权。你将创建一个 DataPacket 类,用于管理动态分配的字节数组,并实现一个移动构造函数,通过“窃取”数据而不是复制数据来完成转移。

你将创建两个文件来组织代码:

  • DataPacket.h:定义一个管理整数数据负载的 DataPacket 类。你的类应包含:
    • 私有成员:指向整数数组的指针(payload)、表示元素数量的 size,以及用于标识数据包的字符串 packetId
    • 一个带参数的构造函数,接收数据包 ID 和大小,分配数组,并用从 0 到 size-1 的值填充数组。打印 "Packet <id> created with size <size>"
    • 一个标记为 noexcept 的移动构造函数,用于转移源对象中负载的所有权。打印 "Packet <id> moved"。记得将源对象保留在有效的空状态(nullptr,大小为 0)
    • 一个析构函数,如果指针不为 null,就释放内存,并打印 "Packet <id> destroyed"
    • 一个 getSize() 方法,返回当前大小
    • 一个 getId() 方法,返回数据包 ID
    • 一个 getSum() 方法,返回负载中所有元素的总和(如果负载为 null,则返回 0)
  • main.cpp:通过从输入中读取数据包 ID 和大小,演示移动构造。然后:
    • 使用输入值创建一个名为 original 的 DataPacket
    • 打印 "Original - ID: <id>, Size: <size>, Sum: <sum>"
    • 使用 std::move() 从 original 移动构造一个名为 transferred 的新数据包
    • 打印 "After move:"
    • 打印 "Original - ID: <id>, Size: <size>, Sum: <sum>"
    • 打印 "Transferred - ID: <id>, Size: <size>, Sum: <sum>"

输入格式如下:

  • 第一行:数据包 ID(字符串)
  • 第二行:大小(整数)

移动之后,原始数据包的大小应为 0,总和应为 0(因为它的数据已被转移),而转移后的数据包应包含全部原始数据。这演示了移动语义如何通过转移指针所有权来避免开销高昂的深拷贝。

在 main.cpp 中使用 #include "DataPacket.h" 引入头文件,并记得引入 <utility> 以使用 std::move()。

亲手试试

#include <iostream>
#include <string>
#include <utility>
#include "DataPacket.h"

using namespace std;

int main() {
    // 读取输入
    string packetId;
    int size;
    cin >> packetId;
    cin >> size;

    // TODO: 用输入值创建一个名为 'original' 的 DataPacket

    // TODO: 输出 "Original - ID: <id>, Size: <size>, Sum: <sum>"

    // TODO: 通过移动 original 创建一个名为 'transferred' 的新数据包
    // 提示:使用 std::move()

    // TODO: 输出 "After move:"

    // TODO: 输出 "Original - ID: <id>, Size: <size>, Sum: <sum>"

    // TODO: 输出 "Transferred - ID: <id>, Size: <size>, Sum: <sum>"

    return 0;
}
quiz icon自我测试

这节课包含一个小测验。开始学习即可作答,并记录你的进度。

面向对象编程中的所有课

自己练习: C++ 在线编译器