C++中的三法则:每个开发者都应了解的内容

面向初学者的在线Python训练器

轻松学习Python,无需理论负担。在浏览器中直接解决带自动检查的实践任务,无需安装任何东西。

开始课程

什么是C++中的三法则?通过示例来解析



如果你曾经用C++编写过管理动态内存的类(例如包含new或malloc),那么你很可能遇到过三法则(Rule of Three)。这是该语言最基本的原则之一,可以防止内存泄漏、双重释放和未定义行为。让我们通过实际示例来剖析它,这样你不仅能记住规则,还能理解它为什么有效。



想象一下:你编写了一个在构造函数中分配内存的类。编译器会友好地为你生成拷贝构造函数和赋值运算符。但它是浅层(shallow copy)地完成的。然后问题就开始了。三法则指出:如果你的类显式定义了析构函数、拷贝构造函数或赋值运算符,那么很可能你需要定义所有这三个。为什么?因为这是一个信号,表明你的类管理着某种资源(内存、文件、网络连接),并且拷贝必须是深层的。



问题:浅拷贝(shallow copy)

让我们看一个典型的例子。类String,它在动态内存中存储C风格字符串:



class String {private:    char* data;    size_t size;public:    // 构造函数    String(const char* str) {        size = strlen(str);        data = new char[size + 1];        strcpy(data, str);    }

// 析构函数 ~String() { delete[] data; }

// 拷贝构造函数 — 由编译器生成(浅拷贝) // 赋值运算符 — 由编译器生成(浅拷贝)

void print() const { std::cout << data << std::endl; }};


一切看起来都很好,直到我们尝试复制对象:



int main() {    String s1("Hello");    String s2 = s1;  // 调用拷贝构造函数(由编译器生成)    s1.print();    s2.print();    // 程序会因double free错误而崩溃!    return 0;}


发生了什么?编译器生成了一个拷贝构造函数,它只是复制了指针data。现在s1.data和s2.data指向同一个内存块。当对象离开作用域时,它们的析构函数被调用。第一个析构函数释放内存,第二个析构函数试图再次释放它——双重释放。程序崩溃或行为不可预测。



解决方案:三法则的实际应用

为了解决这种情况,需要实现所有三个特殊的成员函数。这就是三法则。让我们编写类的正确版本:



class String {private:    char* data;    size_t size;public:    // 构造函数    String(const char* str) {        size = strlen(str);        data = new char[size + 1];        strcpy(data, str);    }

// 析构函数 ~String() { delete[] data; }

// 拷贝构造函数 — 深拷贝 String(const String& other) { size = other.size; data = new char[size + 1]; strcpy(data, other.data); }

// 赋值运算符 — 深拷贝 String& operator=(const String& other) { if (this == &other) return *this; // 防止自赋值

delete[] data; // 释放旧资源

size = other.size; data = new char[size + 1]; strcpy(data, other.data);

return *this; }

void print() const { std::cout << data << std::endl; }};


现在拷贝工作正常:每个对象拥有自己的内存块。赋值也正常

博客

书籍推荐