You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在含继承与虚函数的类中应用C++五大法则(Rule of Five)

如何给C数组管理动态资源的类实现五大法则(Rule of Five)

首先得明确:五大法则的核心是当类持有动态资源(比如你这里的C风格数组、FILE指针、数据库句柄)时,必须手动控制资源的拷贝、移动和销毁逻辑,以此避免内存泄漏、二次释放、浅拷贝野指针这类经典问题。既然你的类没有继承和多态,我们不用操心虚析构的事,只聚焦资源管理本身就行。

先假设你的基础类是这样的(典型的C数组管理场景):

class DynamicArray {
private:
    int* m_data; // C风格动态数组指针
    size_t m_size; // 数组元素个数
public:
    // 普通构造函数:分配C数组
    explicit DynamicArray(size_t size) : m_size(size), m_data(new int[size]) {}

    // 这里原来没有五大法则的函数,会导致浅拷贝问题
};

下面一步步实现五大法则的五个核心函数:


1. 析构函数:释放资源

这是最基础的一步,必须手动实现来释放动态分配的C数组,否则会造成内存泄漏:

~DynamicArray() {
    delete[] m_data; // 注意用delete[],对应new[]分配的数组
    m_data = nullptr; // 可选但推荐:置空避免野指针问题
}

2. 拷贝构造函数:深拷贝资源

如果不手动实现,编译器会生成默认的浅拷贝构造函数,导致两个DynamicArray对象共享同一个m_data指针——当其中一个对象析构时,数组被释放,另一个对象再访问就会触发未定义行为。

我们需要实现深拷贝:创建一个新的数组,把原对象的元素逐个复制过来:

// 拷贝构造:用const引用接收原对象
DynamicArray(const DynamicArray& other) : m_size(other.m_size), m_data(new int[other.m_size]) {
    // 复制数组元素
    for (size_t i = 0; i < m_size; ++i) {
        m_data[i] = other.m_data[i];
    }
}

3. 拷贝赋值运算符:安全的深拷贝赋值

默认的拷贝赋值同样会做浅拷贝,存在共享资源的风险。这里推荐用swap idiom实现,既保证异常安全(内存分配失败时原对象状态不会被破坏),又代码简洁:

// 拷贝赋值:返回引用支持链式赋值
DynamicArray& operator=(const DynamicArray& other) {
    // 先创建临时对象完成深拷贝
    DynamicArray temp(other);
    // 交换临时对象和当前对象的资源
    swap(*this, temp);
    // 临时对象析构时会自动释放当前对象原来的旧资源
    return *this;
}

// 辅助swap函数(通常作为类的友元)
friend void swap(DynamicArray& first, DynamicArray& second) noexcept {
    using std::swap;
    swap(first.m_data, second.m_data);
    swap(first.m_size, second.m_size);
}

4. 移动构造函数:转移资源所有权

当对象是右值(比如临时对象、std::move后的对象)时,我们不需要做耗时的深拷贝,直接把资源从原对象转移到新对象即可,效率极高。

移动构造的核心是:接管原对象的资源,然后把原对象置为“空”状态(避免原对象析构时释放已经转移的资源):

// 移动构造:用右值引用接收原对象,标记noexcept保证异常安全
DynamicArray(DynamicArray&& other) noexcept : m_data(nullptr), m_size(0) {
    swap(*this, other);
}

也可以直接手动赋值实现,效果一样:

DynamicArray(DynamicArray&& other) noexcept : m_data(other.m_data), m_size(other.m_size) {
    other.m_data = nullptr;
    other.m_size = 0;
}

加上noexcept很重要:标准库容器会优先选择标记为noexcept的移动构造函数,避免异常安全问题。


5. 移动赋值运算符:转移资源的赋值

和移动构造逻辑类似,把右值对象的资源转移过来,同时保证原对象能安全析构:

// 移动赋值:返回引用,标记noexcept
DynamicArray& operator=(DynamicArray&& other) noexcept {
    swap(*this, other);
    return *this;
}

如果担心自赋值,也可以手动实现更严谨的版本:

DynamicArray& operator=(DynamicArray&& other) noexcept {
    if (this != &other) { // 避免自赋值
        delete[] m_data; // 先释放当前对象的旧资源
        // 接管原对象的资源
        m_data = other.m_data;
        m_size = other.m_size;
        // 置空原对象
        other.m_data = nullptr;
        other.m_size = 0;
    }
    return *this;
}

完整的类代码

把上面的部分整合起来,就是完整的实现:

#include <algorithm> // 用于std::swap

class DynamicArray {
private:
    int* m_data;
    size_t m_size;

    // 友元swap函数
    friend void swap(DynamicArray& first, DynamicArray& second) noexcept {
        using std::swap;
        swap(first.m_data, second.m_data);
        swap(first.m_size, second.m_size);
    }

public:
    // 普通构造函数
    explicit DynamicArray(size_t size) : m_size(size), m_data(new int[size]) {}

    // 析构函数
    ~DynamicArray() {
        delete[] m_data;
        m_data = nullptr;
    }

    // 拷贝构造函数
    DynamicArray(const DynamicArray& other) : m_size(other.m_size), m_data(new int[other.m_size]) {
        for (size_t i = 0; i < m_size; ++i) {
            m_data[i] = other.m_data[i];
        }
    }

    // 拷贝赋值运算符
    DynamicArray& operator=(const DynamicArray& other) {
        DynamicArray temp(other);
        swap(*this, temp);
        return *this;
    }

    // 移动构造函数
    DynamicArray(DynamicArray&& other) noexcept : m_data(nullptr), m_size(0) {
        swap(*this, other);
    }

    // 移动赋值运算符
    DynamicArray& operator=(DynamicArray&& other) noexcept {
        swap(*this, other);
        return *this;
    }

    // 可选:添加访问接口
    int& operator[](size_t index) {
        return m_data[index];
    }

    const int& operator[](size_t index) const {
        return m_data[index];
    }

    size_t size() const {
        return m_size;
    }
};

关键注意点

  • 对于C数组,必须用delete[]释放,对应new[]的分配,不能用delete,否则会触发未定义行为。
  • 移动操作一定要把原对象的资源指针置空,避免原对象析构时重复释放资源。
  • 拷贝操作必须是深拷贝,绝对不能共享资源。
  • 如果你的动态资源是FILE指针、数据库句柄,逻辑完全通用:析构时调用fclose/db_close,拷贝时创建新的句柄(比如复制文件连接、新建数据库连接),移动时转移句柄并把原句柄置为无效状态。

内容的提问来源于stack exchange,提问作者code

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.21 04:31:53