C++拷贝构造函数详解:从浅拷贝到深拷贝

什么是拷贝构造函数?

拷贝构造函数是C++中的一种特殊构造函数,用于创建一个新对象作为现有对象的副本。当我们使用一个对象来初始化同类型的另一个对象时,拷贝构造函数就会被调用。

基本语法

class MyClass {
public:
    // 拷贝构造函数
    MyClass(const MyClass& other) {
        // 拷贝操作
    }
};

何时调用拷贝构造函数?

拷贝构造函数在以下情况下被调用:

  1. 对象初始化

    MyClass obj1;
    MyClass obj2 = obj1; // 拷贝构造函数被调用
    MyClass obj3(obj1);  // 拷贝构造函数被调用
    
  2. 函数参数传递(按值传递):

    void doSomething(MyClass obj) {
        // 函数体
    }
    
    MyClass myObj;
    doSomething(myObj); // 拷贝构造函数被调用
    
  3. 函数返回值(按值返回):

    MyClass createObject() {
        MyClass obj;
        return obj; // 可能调用拷贝构造函数(取决于编译器优化)
    }
    

默认拷贝构造函数的问题:浅拷贝

如果你没有自定义拷贝构造函数,编译器会自动生成一个默认的拷贝构造函数。这个默认实现执行浅拷贝(成员逐一复制),对于简单类型没问题,但对于指针成员会导致问题:

class ShallowCopy {
private:
    int* data;
    int size;
public:
    ShallowCopy(int sz) : size(sz) {
        data = new int[size];
    }
    
    // 默认拷贝构造函数执行浅拷贝:
    // data = other.data; (复制指针值,而不是指向的内容)
    // size = other.size;
    
    ~ShallowCopy() {
        delete[] data;
    }
};

使用这个类会导致双重释放问题:

ShallowCopy obj1(10);
ShallowCopy obj2 = obj1; // 浅拷贝:两个对象的data指向同一内存
// obj1和obj2析构时都会尝试释放同一块内存 → 崩溃!

解决方案:深拷贝

为了解决浅拷贝的问题,我们需要自定义拷贝构造函数来实现深拷贝

class DeepCopy {
private:
    int* data;
    int size;
public:
    DeepCopy(int sz) : size(sz) {
        data = new int[size];
    }
    
    // 自定义拷贝构造函数(深拷贝)
    DeepCopy(const DeepCopy& other) : size(other.size) {
        data = new int[size];
        for (int i = 0; i < size; ++i) {
            data[i] = other.data[i]; // 复制内容,而不是指针
        }
    }
    
    ~DeepCopy() {
        delete[] data;
    }
};

完整的示例代码

#include <iostream>
#include <cstring>

class String {
private:
    char* str;
    int length;
    
public:
    // 普通构造函数
    String(const char* s = "") {
        length = std::strlen(s);
        str = new char[length + 1];
        std::strcpy(str, s);
    }
    
    // 拷贝构造函数(深拷贝)
    String(const String& other) : length(other.length) {
        str = new char[length + 1];
        std::strcpy(str, other.str);
        std::cout << "拷贝构造函数被调用" << std::endl;
    }
    
    // 析构函数
    ~String() {
        delete[] str;
    }
    
    // 显示字符串
    void display() const {
        std::cout << str << std::endl;
    }
    
    // 修改字符串
    void update(const char* s) {
        delete[] str;
        length = std::strlen(s);
        str = new char[length + 1];
        std::strcpy(str, s);
    }
};

int main() {
    String s1("Hello");
    String s2 = s1; // 拷贝构造函数被调用
    
    std::cout << "s1: ";
    s1.display();
    std::cout << "s2: ";
    s2.display();
    
    s1.update("World"); // 修改s1,不影响s2
    
    std::cout << "修改后:" << std::endl;
    std::cout << "s1: ";
    s1.display();
    std::cout << "s2: ";
    s2.display();
    
    return 0;
}

拷贝构造函数与赋值运算符的区别

初学者常常混淆拷贝构造函数和赋值运算符,它们的区别在于:

  • 拷贝构造函数:创建新对象时使用
  • 赋值运算符:已存在的对象被赋予新值时使用
MyClass obj1;
MyClass obj2 = obj1; // 拷贝构造函数
MyClass obj3;
obj3 = obj1;         // 赋值运算符

禁止拷贝

有时候我们想禁止对象的拷贝行为,C++11之前可以通过将拷贝构造函数声明为private来实现,C++11之后可以使用delete关键字:

class NonCopyable {
public:
    NonCopyable() = default;
    
    // 禁止拷贝
    NonCopyable(const NonCopyable&) = delete;
    NonCopyable& operator=(const NonCopyable&) = delete;
};

最佳实践

  1. 三分法则:如果你需要自定义析构函数、拷贝构造函数或拷贝赋值运算符中的任何一个,你可能需要自定义所有这三个。

  2. 考虑使用智能指针:现代C++中,使用std::unique_ptrstd::shared_ptr可以避免许多手动内存管理的问题。

  3. 优先使用const引用传递对象:避免不必要的拷贝,提高性能。

  4. 考虑移动语义:C++11引入了移动构造函数和移动赋值运算符,对于管理资源的类,实现移动语义可以显著提高性能。

总结

拷贝构造函数是C++中管理对象复制行为的重要工具。理解浅拷贝和深拷贝的区别对于编写正确、安全的C++代码至关重要。对于包含动态分配资源的类,几乎总是需要自定义拷贝构造函数来实现深拷贝,避免潜在的内存问题和未定义行为。

在现代C++开发中,随着智能指针和移动语义的引入,我们需要手动实现拷贝构造函数的情况减少了,但理解其原理仍然非常重要。

posted @ 2025-09-16 17:56  魔高一丈  阅读(61)  评论(0)    收藏  举报