C++零之法制和三五法则

C++零之法制和三五法则

三之法则 (The Rule of Three) - C++98时代

如果一个类需要自定义以下三者中的任意一个 ,那么它几乎肯定需要全部三个

  1. 析构函数
  2. 拷贝构造函数
  3. 拷贝赋值运算符

当一个类直接管理裸资源 时(例如,通过new/delete管理裸指针,或通过fopen/fclose管理文件句柄)

c++ 复制代码
class MyString_RuleOf3 {
private:
    char* data_;
public:
    MyString_RuleOf3(const char* s = "") {
        data_ = new char[strlen(s) + 1];
        strcpy(data_, s);
    }
    
    // 1. 析构函数 (因为有new,所以必须有delete)
    ~MyString_RuleOf3() {
        delete[] data_;
    }

    // 2. 拷贝构造函数 (必须深拷贝,否则两个对象指向同一内存)
    MyString_RuleOf3(const MyString_RuleOf3& other) {
        data_ = new char[strlen(other.data_) + 1];
        strcpy(data_, other.data_);
    }

    // 3. 拷贝赋值运算符 (同上,需要深拷贝并处理自我赋值)
    MyString_RuleOf3& operator=(const MyString_RuleOf3& other) {
        if (this != &other) {
            delete[] data_; // 释放旧资源
            data_ = new char[strlen(other.data_) + 1]; // 分配新资源
            strcpy(data_, other.data_); // 拷贝内容
        }
        return *this;
    }
};

五之法则 (The Rule of Five) - C++11时代

随着C++11引入移动语义,三之法则扩展为五之法则

法则内容 :如果一个类定义了五个特殊成员函数中的任何一个,它应该把它们全部定义出来(或者将不需要的删除 = delete

  1. 析构函数
  2. 拷贝构造函数
  3. 拷贝赋值运算符
  4. 移动构造函数 (Move Constructor)
  5. 移动赋值运算符 (Move Assignment Operator)

同三之法则,但通过添加移动操作来提升性能,避免对临时对象进行不必要的深拷贝

c++ 复制代码
// 续上例...
// 4. 移动构造函数 (窃取资源,而非拷贝)
MyString_RuleOf5(MyString_RuleOf5&& other) noexcept {
    data_ = other.data_; // 直接拿走指针
    other.data_ = nullptr; // 将源对象置于安全可析构的状态
}

// 5. 移动赋值运算符
MyString_RuleOf5& operator=(MyString_RuleOf5&& other) noexcept {
    if (this != &other) {
        delete[] data_; // 释放自己的旧资源
        data_ = other.data_; // 窃取源的资源
        other.data_ = nullptr;
    }
    return *this;
}

零之法则 (The Rule of Zero) - 现代C++的理想

法则内容类应该只负责业务逻辑,而不应直接管理任何资源。 相反,它应该使用那些本身就遵循"五之法则"的资源管理类 (如std::string, std::vector, std::unique_ptr)来持有资源;这样,一个特殊成员函数都不需要写

核心思想:将资源管理的工作委托给标准库。编译器自动生成的默认特殊成员函数会完美地完成工作,因为它们会自动调用其成员变量对应的函数

  1. 默认析构函数 会自动调用std::string的析构函数(它会释放内存)
  2. 默认拷贝构造函数 会自动调用std::string的拷贝构造函数(它会执行深拷贝)
  3. 默认移动构造函数 会自动调用std::string的移动构造函数(它会窃取资源)
c++ 复制代码
#include <string>
#include <utility>

class MyString_RuleOf0 {
private:
    std::string data_; // 直接使用std::string管理资源

public:
    // 构造函数只关心业务逻辑
    MyString_RuleOf0(const std::string& s) : data_(s) {}

    // 析构函数?不需要写,编译器默认生成。
    // 拷贝构造?不需要写,编译器默认生成。
    // 拷贝赋值?不需要写,编译器默认生成。
    // 移动构造?不需要写,编译器默认生成。
    // 移动赋值?不需要写,编译器默认生成。
};

零之法则 是现代C++的首选设计目标。它通过组合和委托,从根本上消除了手动管理资源的需要

数据组合必须直接管理裸资源

通常发生于和C语言库或底层操作系统API交互时。

例如,一个API返回一个需要手动释放的句柄(handle)和一块需要手动free的内存(buffer

  1. 创建一个专门的RAII包装类(遵循五之法则)

    c++ 复制代码
    struct C_ApiResource { void* handle; char* buffer; };
    C_ApiResource* create_resource();
    void destroy_resource(C_ApiResource* res);
  2. 需要创建一个C++包装类来管理C_ApiResource

    c++ 复制代码
    // 这个类是底层RAII包装器,它必须遵循"五之法则"
    class ApiResourceHandle {
    private:
        C_ApiResource* resource_ = nullptr; // 直接管理裸资源
    
    public:
        // 构造函数获取资源
        ApiResourceHandle() : resource_(create_resource()) {}
    
        // 1. 析构函数:释放资源
        ~ApiResourceHandle() {
            if (resource_) {
                destroy_resource(resource_);
            }
        }
    
        // 2. 删除拷贝操作,确保资源所有权唯一
        ApiResourceHandle(const ApiResourceHandle&) = delete;
        ApiResourceHandle& operator=(const ApiResourceHandle&) = delete;
    
        // 4. 移动构造函数:转移所有权
        ApiResourceHandle(ApiResourceHandle&& other) noexcept 
            : resource_(other.resource_) {
            other.resource_ = nullptr;
        }
    
        // 5. 移动赋值运算符:转移所有权
        ApiResourceHandle& operator=(ApiResourceHandle&& other) noexcept {
            if (this != &other) {
                if (resource_) {
                    destroy_resource(resource_);
                }
                resource_ = other.resource_;
                other.resource_ = nullptr;
            }
            return *this;
        }
    
        // ... 其他操作资源的接口 ...
    };
  3. 在高层级类中使用这个包装类(享受零之法则)

    c++ 复制代码
    class HighLevelProcessor {
    private:
        // 使用我们自己封装好的RAII包装类
        ApiResourceHandle handle_; 
    
    public:
        HighLevelProcessor() {} // 默认构造函数即可
    
        // HighLevelProcessor现在可以遵循"零之法则"了!
        // 编译器为它生成的特殊成员函数会自动调用ApiResourceHandle对应的版本。
    };

自定义数据组合:用标准库组件(std::string, std::vector, std::unique_ptr等)构成,那么直接组合它们,并让所有类都遵循"零之法则"

必须与C库等底层接口打交道,管理裸资源组合:那么就创建一个专门的底层RAII包装类,让这个类遵循"五之法则" 。然后,程序的其余部分就可以使用这个包装类,并继续享受"零之法则"带来的简洁与安全

相关推荐
OKkankan38 分钟前
string类的模拟实现
开发语言·数据结构·c++·算法
charlie1145141918 小时前
设计自己的小传输协议 导论与概念
c++·笔记·qt·网络协议·设计·通信协议
程序员编程指南11 小时前
Qt 并行计算框架与应用
c语言·数据库·c++·qt·系统架构
努力的小帅12 小时前
C++_红黑树树
开发语言·数据结构·c++·学习·算法·红黑树
CN-Dust12 小时前
【C++】指针
开发语言·c++
逐花归海.12 小时前
『 C++ 入门到放弃 』- 哈希表
数据结构·c++·程序人生·哈希算法·散列表
筏.k13 小时前
C++现代Redis客户端库redis-plus-plus详解
c++·redis
程序员编程指南13 小时前
Qt 多线程调试技巧与常见问题
c语言·开发语言·c++·qt
徐归阳14 小时前
第十一天:不定方程求解
c++·visual studio
1白天的黑夜114 小时前
前缀和-974.和可被k整除的子数组-力扣(LeetCode)
c++·leetcode·前缀和