C++ 拷贝构造函数 浅拷贝 深拷贝

C++ 的拷贝构造函数(Copy Constructor)是一种特殊的构造函数,用于通过已有对象初始化新创建的对象。它在对象复制场景中起关键作用,尤其在涉及动态内存管理时需特别注意深浅拷贝问题。

一、定义与语法​​

拷贝构造函数的​​参数为本类对象的常量引用​​,格式为 ClassName(const ClassName &src)。若未显式定义,​​编译器会自动生成默认拷贝构造函数​​,执行​​浅拷贝​​(逐成员复制值)。

二、调用时机​​

拷贝构造函数在以下场景被调用:

  • 对象初始化​​:通过已有对象显式或隐式创建新对象(如 Student b = a; 或 Student b(a);)。
  • ​​值传递参数​​:对象以值传递方式传入函数。
  • 函数返回值​​:对象以值传递方式从函数返回。
  • 异常处理​​:抛出或捕获异常对象时可能触发。

三、深浅拷贝问题​

1. ​​浅拷贝的风险​​

默认拷贝构造函数仅复制成员的值。若类包含​​指针成员​​,浅拷贝会导致新对象与原对象指向同一内存地址,析构时引发重复释放错误。

2. ​​深拷贝的实现​​

需​​显式定义拷贝构造函数​​,为指针成员分配独立内存并复制内容。

四、与赋值运算符的区别​​

  • 拷贝构造函数​​:在​​对象创建时​​调用,用于初始化新对象。
  • 赋值运算符​​:在​​已存在对象​​间赋值时调用(如 a = b;),需重载 operator=。
  • 关键区别​​:
Student c = a; // 调用拷贝构造函数(对象c尚未存在)
c = b;         // 调用赋值运算符(对象c已存在)

五、最佳实践​​

  • 遵循三法则​​:若类需自定义拷贝构造函数、析构函数或赋值运算符之一,通常需同时实现三者。
  • 禁用拷贝​​:可通过 = delete 显式删除拷贝构造函数,禁止对象复制(如单例模式)。
  • 优化性能​​:优先使用 const & 传递对象参数,避免不必要的拷贝。

六、浅拷贝与深拷贝对比示例​

#include <iostream>
#include <cstring>
using namespace std;

// 浅拷贝导致的问题
class ShallowString {
public:
    char* data;
    // 构造函数
    ShallowString(const char* s) {
        data = new char[strlen(s) + 1];
        strcpy(data, s);
    }
    // 默认拷贝构造函数(浅拷贝)
    ~ShallowString() { delete[] data; }
};

// 深拷贝解决方案
class DeepString {
public:
    char* data;
    // 构造函数
    DeepString(const char* s) {
        data = new char[strlen(s) + 1];
        strcpy(data, s);
    }
    // 自定义深拷贝构造函数
    DeepString(const DeepString& other) {
        data = new char[strlen(other.data) + 1];
        strcpy(data, other.data);
    }
    // 析构函数
    ~DeepString() { delete[] data; }
};

int main() {
    // 浅拷贝问题演示
    ShallowString a("Hello");
    ShallowString b = a;  // 默认浅拷贝
    cout << "浅拷贝结果: " << a.data << " | " << b.data << endl;
    // 运行时崩溃:a和b析构时重复释放同一内存

    // 深拷贝解决方案
    DeepString c("World");
    DeepString d = c;      // 调用深拷贝构造函数
    strcpy(c.data, "C++"); // 修改原对象不影响副本
    cout << "深拷贝结果: " << c.data << " | " << d.data << endl;
    return 0;
}

输出​​(浅拷贝运行崩溃,深拷贝输出):

浅拷贝结果: Hello | Hello
深拷贝结果: C++ | World

​​关键点​​:

  • 浅拷贝类未定义拷贝构造函数,导致重复释放内存
  • 深拷贝类显式分配独立内存,确保对象独立性

七、拷贝构造函数调用时机示例​

#include <iostream>
using namespace std;

class Logger {
public:
    int id;
    // 构造函数
    Logger(int x) : id(x) { 
        cout << "构造对象" << id << endl; 
    }
    // 拷贝构造函数
    Logger(const Logger& src) : id(src.id + 100) {
        cout << "拷贝构造对象" << id << endl;
    }
};

// 值传递触发拷贝构造
void process(Logger obj) {
    cout << "处理对象" << obj.id << endl;
}

// 返回值触发拷贝构造
Logger create() {
    Logger temp(999);
    return temp;  // 触发拷贝构造(编译器可能优化)
}

int main() {
    Logger a(1);                 // 调用普通构造函数
    Logger b = a;               // 调用拷贝构造函数[3](@ref)
    process(a);                  // 值传递触发拷贝构造[4](@ref)
    Logger c = create();         // 返回值触发拷贝构造
    return 0;
}
构造对象1
拷贝构造对象101
拷贝构造对象101
处理对象101
构造对象999
拷贝构造对象1099
Logger b = a 显式初始化触发拷贝
process(a) 值传递参数触发拷贝
返回值时编译器可能优化(RVO),但逻辑上仍存在拷贝过程

八、完整深拷贝类示例(含赋值运算符)

#include <iostream>
#include <cstring>
using namespace std;

class Vector {
private:
    int* arr;
    size_t size;
public:
    // 构造函数
    Vector(size_t s) : size(s) {
        arr = new int[size]{0};
        cout << "构造Vector(大小" << size << ")" << endl;
    }
    
    // 深拷贝构造函数
    Vector(const Vector& src) : size(src.size) {
        arr = new int[size];
        memcpy(arr, src.arr, size * sizeof(int));
        cout << "深拷贝Vector" << endl;
    }
    
    // 赋值运算符重载
    Vector& operator=(const Vector& rhs) {
        if (this != &rhs) {
            delete[] arr;          // 释放原有资源
            size = rhs.size;
            arr = new int[size];
            memcpy(arr, rhs.arr, size * sizeof(int));
            cout << "赋值运算符调用" << endl;
        }
        return *this;
    }
    
    // 析构函数
    ~Vector() { 
        delete[] arr; 
        cout << "销毁Vector" << endl;
    }
    
    void set(int index, int value) { arr[index] = value; }
    void print() {
        cout << "[ ";
        for (size_t i = 0; i < size; ++i)
            cout << arr[i] << " ";
        cout << "]" << endl;
    }
};

int main() {
    Vector v1(3);
    v1.set(0, 10);
    v1.set(1, 20);
    v1.set(2, 30);
    
    Vector v2 = v1;        // 调用拷贝构造函数
    v2.set(1, 99);         // 修改副本不影响原对象
    
    Vector v3(2);
    v3 = v1;               // 调用赋值运算符
    
    cout << "v1: ";
    v1.print();  // [10 20 30]
    cout << "v2: ";
    v2.print();  // [10 99 30]
    cout << "v3: ";
    v3.print();  // [10 20 30]
    
    return 0;
}
构造Vector(大小3)
深拷贝Vector
构造Vector(大小2)
赋值运算符调用
v1: [ 10 20 30 ]
v2: [ 10 99 30 ]
v3: [ 10 20 30 ]
销毁Vector
销毁Vector
销毁Vector
设计原则​​:
  1. 遵循三法则:同时实现拷贝构造、析构和赋值运算符
  2. 自赋值检查:if (this != &rhs) 防止资源错误释放
  3. 深拷贝保证:memcpy复制数据而非指针地址

九、禁用拷贝的示例​

class NonCopyable {
public:
    NonCopyable() = default;
    // 删除拷贝构造函数和赋值运算符
    NonCopyable(const NonCopyable&) = delete;
    NonCopyable& operator=(const NonCopyable&) = delete;
};

int main() {
    NonCopyable obj1;
    // NonCopyable obj2 = obj1;  // 编译错误
    // obj1 = NonCopyable();     // 编译错误
    return 0;
}

应用场景​​:单例模式、资源独占类等需要禁止复制的场景

十、浅拷贝何深拷贝的本质区别是什么

浅拷贝仅复制指针地址,深拷贝递归复制所有数据到独立内存

十一、使用智能指针避免手动管理

#include <iostream>
#include <memory>
using namespace std;

class SafeString {
public:
    shared_ptr<char[]> data;  // 共享指针自动管理内存
    
    // 构造函数
    SafeString(const char* s) {
        data = make_shared<char[]>(strlen(s) + 1);
        strcpy(data.get(), s);
        cout << "构造 SafeString: " << data.get() << endl;
    }
    
    // 无需定义拷贝构造函数和析构函数!
};

int main() {
    SafeString s1("Modern C++");
    SafeString s2 = s1;  // 共享指针引用计数+1
    
    strcpy(s1.data.get(), "Updated");
    cout << "s1.data: " << s1.data.get() << endl;
    cout << "s2.data: " << s2.data.get() << endl;
    
    return 0;  // 自动释放内存,无崩溃
}
构造 SafeString: Modern C++
s1.data: Updated
s2.data: Updated  // 共享同一内存但无双重释放风险
关键点​​:
  • 使用 shared_ptr 自动管理内存生命周期
  • 适用于资源共享场景,无需手动实现深拷贝
注意:

虽然使用了shared_ptr,但是SafeString s2 = s1依然是浅拷贝,只是不报错罢了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值