导读:本期聚焦于老毕创作的《C++怎么实现深拷贝和浅拷贝?拷贝构造函数进阶用法详解》,敬请观看详情。当类里包含指针成员却只写了简单的赋值语句,程序往往在析构时崩溃。根源在于浅拷贝仅复制指针地址,多个对象共享同一块堆内存。真正安全的做法是自定义拷贝构造函数,在构造函数内用new重新申请空间并逐字节复制内容,这便是深拷贝。本文从内存布局讲清二者差异,展示未重载时的默认行为缺陷,并给出带指针资源的字符串类完整实现。同时说明拷贝赋值运算符与禁止拷贝的场景,帮助你在设计类时避开重复释放与悬空指针问题。

在C++类设计中,拷贝行为直接决定了对象生命周期的安全程度。如果一个类管理了动态内存、文件句柄或网络连接,编译器生成的默认拷贝逻辑就可能埋下严重隐患。理解深拷贝与浅拷贝的本质区别,并掌握拷贝构造函数的正确写法,是编写健壮C++代码的基本功。很多运行时崩溃、内存泄漏和双重释放问题,追根溯源都是拷贝语义处理不当造成的。

C++怎么实现深拷贝和浅拷贝?拷贝构造函数进阶用法详解

浅拷贝的底层机制与典型危害

浅拷贝指的是在对象复制时,仅仅将成员变量的值逐位复制一份。对于基本类型如intdouble来说这没有问题;但当类中含有指针成员且指向堆区内存时,浅拷贝只会复制指针变量里保存的地址值,而不会新建一块内存。结果就是原对象和新对象的指针指向同一块内存区域。

这种共享在析构时会引发灾难。假设原对象和拷贝对象先后离开作用域,析构函数会被调用两次,对同一块堆内存执行两次delete,直接导致程序崩溃或产生未定义行为。此外,任何一方通过指针修改内存内容,另一方也会被动看到变化,破坏了对象的封装性。下面这段代码展示了未自定义拷贝构造函数时的危险行为。

#include <iostream>
#include <cstring>

class ShallowString {
public:
    char* data;
    ShallowString(const char* s) {
        data = new char[strlen(s) + 1];
        strcpy(data, s);
    }
    ~ShallowString() {
        delete[] data;
    }
};

int main() {
    ShallowString a("hello");
    ShallowString b = a; // 默认浅拷贝,b.data 与 a.data 指向同一地址
    // 函数结束时会先析构 b 再析构 a,对同一地址 delete 两次,崩溃
    return 0;
}

从上面的例子可以看到,编译器提供的默认拷贝构造函数等价于依次复制每个成员,对指针不做深层的资源复制。在真实项目中,只要类负责资源释放,就必须警惕这种隐式行为。很多初学者会疑惑为什么程序退出时报告堆损坏,其实多数情况就是浅拷贝带来的重复释放。

深拷贝的实现方式与拷贝构造函数编写

深拷贝要求在复制对象时,为指针所指向的资源重新分配独立的内存,并把原内容完整复制过去。这样每个对象都拥有自己的资源副本,生命周期互不干扰。实现深拷贝的核心位置就是自定义拷贝构造函数,其签名通常为ClassName(const ClassName& other)

在拷贝构造函数内部,需要先为指针成员分配新内存,然后使用strcpymemcpy或循环赋值等方式把原对象的数据搬过来。注意参数必须是常量引用,避免拷贝构造自身无限递归。以下示例将前面的类改写为安全的深拷贝版本,并附带了拷贝赋值运算符,保证赋值场景同样安全。

#include <iostream>
#include <cstring>

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& operator=(const DeepString& other) {
        if (this != &other) {
            delete[] data;
            data = new char[strlen(other.data) + 1];
            strcpy(data, other.data);
        }
        return *this;
    }
    ~DeepString() {
        delete[] data;
    }
    void print() const {
        std::cout << data << std::endl;
    }
};

int main() {
    DeepString a("deep");
    DeepString b = a; // 深拷贝,b拥有独立内存
    a.data[0] = 'X';  // 修改 a 不影响 b
    b.print();        // 依然输出 deep
    return 0;
}

深拷贝虽然安全,但会带来额外的内存和性能开销,尤其是大对象频繁拷贝时。因此在C++11之后,可以结合移动语义,用右值引用和std::move把资源所有权转移出去,避免不必要的复制。但对于必须保留原对象的场景,深拷贝仍不可替代。

还有一个细节是异常安全。如果在深拷贝过程中new抛出了std::bad_alloc,应当保证对象处于有效状态或不泄露已分配内存。上面的写法先分配再赋值,若分配失败异常会直接传播,原对象未被破坏,属于基本异常安全级别。更复杂的类建议使用先拷贝到临时对象再交换的策略。

进阶场景:禁止拷贝与智能指针替代方案

并非所有类都适合拷贝。像互斥锁、文件句柄或网络连接这类资源,语义上不允许复制,这时应当显式禁止拷贝。传统做法是将拷贝构造函数和拷贝赋值运算符声明为private且不实现;C++11起更推荐在函数后面写= delete,让编译器直接拒绝任何拷贝尝试,错误信息也更清晰。

现代C++还提倡用std::unique_ptrstd::shared_ptr管理资源,从根本上简化拷贝语义。unique_ptr默认不可拷贝,只能移动,契合独占资源场景;shared_ptr通过引用计数实现受控共享,拷贝时只增加计数而不复制底层数据。如果类内部仅持有智能指针,通常无需手写深拷贝逻辑。下面演示用unique_ptr避免手动管理的写法。

#include <memory>
#include <string>

class SafeBuffer {
public:
    std::unique_ptr<char[]> buf;
    SafeBuffer(const char* s) {
        size_t len = std::strlen(s) + 1;
        buf = std::make_unique<char[]>(len);
        std::strcpy(buf.get(), s);
    }
    // 拷贝构造被编译器隐式删除,因为 unique_ptr 不可拷贝
    // 若需要深拷贝可手动实现:
    SafeBuffer(const SafeBuffer& other) {
        size_t len = std::strlen(other.buf.get()) + 1;
        buf = std::make_unique<char[]>(len);
        std::strcpy(buf.get(), other.buf.get());
    }
};

可以看到,即便使用智能指针,当你确实需要深拷贝语义时,仍然要在拷贝构造函数里显式分配新资源。区别在于资源的释放由智能指针自动完成,你不必再写析构函数中的delete,大幅降低了出错概率。理解深浅拷贝不仅是为了应对面试提问,更是构建稳定系统的必备知识。

总结来说,当类直接管理裸指针或系统资源时,必须思考拷贝行为;默认浅拷贝只复制地址,容易引发双重释放。通过自定义拷贝构造函数与拷贝赋值实现深拷贝,可让每个对象独占资源。对不可复制的资源使用= deleteunique_ptr,对需要共享的场景使用shared_ptr,能在安全和效率之间取得平衡。

deep_copyshallow_copycopy_constructor修改时间:2026-08-17 22:14:20

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。