C++拷贝构造函数的发生时机,深拷贝实现

在 C++ 中,拷贝构造函数 (Copy Constructor)是一种特殊的构造函数,用于用一个已存在的对象初始化一个新的同类对象。它的调用时机和深拷贝实现密切相关,尤其当类中包含指针成员动态资源时。


一、拷贝构造函数的调用时机

根据权威资料(如 378),拷贝构造函数在以下 三种情况 下会被自动调用:

  1. 用一个对象初始化另一个对象

    cpp 复制代码
    Monster m1(5, "poison", {10, 20});
    Monster m2(m1);        // 调用拷贝构造函数
    Monster m3 = m1;       // 等价于上一行,也调用拷贝构造函数,相当于自动隐式转换成显式的M m3 = M(m1)
  2. 对象以值传递方式作为函数参数

    cpp 复制代码
    void func(Monster m);  // 参数按值传递
    func(m1);              // 调用拷贝构造函数
  3. 函数以值方式返回一个局部对象

    cpp 复制代码
    Monster createMonster() {
        Monster m(3, "burn", {5});
        return m;          // 返回时调用拷贝构造函数(可能被 RVO 优化,但语义上会调用)
    }

⚠️ 注意:如果未显式定义拷贝构造函数,编译器会生成默认的浅拷贝版本 ,对 char* effect 这类指针成员仅复制地址,导致多个对象共享同一块内存,析构时可能重复释放,引发未定义行为。


二、Monster 类的深拷贝实现

假设类定义如下:

cpp 复制代码
class Monster {
public:
    int level;
    char* effect;           // 指向动态分配的字符串
    std::vector<int> atks;  // 标准库容器,自带深拷贝语义
};

✅ 正确实现深拷贝需重写:

  • 拷贝构造函数
  • 拷贝赋值运算符
  • 析构函数

这称为 "三法则"(Rule of Three)。

完整示例代码:

cpp 复制代码
#include <iostream>
#include <vector>
#include <cstring>

class Monster {
public:
    int level;
    char* effect;
    std::vector<int> atks;

    // 构造函数
    Monster(int l, const char* eff, const std::vector<int>& a)
        : level(l), atks(a) {
        if (eff) {
            effect = new char[std::strlen(eff) + 1];
            std::strcpy(effect, eff);
        } else {
            effect = nullptr;
        }
    }

    // 拷贝构造函数(深拷贝)
    Monster(const Monster& other)
        : level(other.level), atks(other.atks) {
        if (other.effect) {
            effect = new char[std::strlen(other.effect) + 1];
            std::strcpy(effect, other.effect);
        } else {
            effect = nullptr;
        }
        std::cout << "深拷贝构造函数被调用\n";
    }

    // 拷贝赋值运算符(深拷贝 + 自赋值安全)
    Monster& operator=(const Monster& other) {
        if (this != &other) {  // 防止自赋值
            delete[] effect;   // 释放原资源

            level = other.level;
            atks = other.atks; // vector 自动深拷贝

            if (other.effect) {
                effect = new char[std::strlen(other.effect) + 1];
                std::strcpy(effect, other.effect);
            } else {
                effect = nullptr;
            }
        }
        return *this;
    }

    // 析构函数
    ~Monster() {
        delete[] effect;
        effect = nullptr;
    }
};

三、深拷贝注意事项(关键点)

问题 说明
1. 指针成员必须手动深拷贝 char* effectnew 新内存并 strcpy 内容,不能只复制指针
2. std::vector<int> 不需要特殊处理 标准库容器自带正确的拷贝语义,会自动深拷贝其内部元素
3. 必须处理自赋值 operator= 中检查 if (this != &other),否则 delete[] 后无法再读取 other.effect
4. 先释放旧资源再分配新资源 避免内存泄漏
5. 遵循"三法则" 有指针成员 → 必须同时定义 析构函数、拷贝构造、拷贝赋值
6. 异常安全建议 更健壮的做法是使用 copy-and-swap 技术,先构造临时副本再交换,保证强异常安全

四、现代 C++ 建议(最佳实践)

为避免手动管理内存,优先使用 RAII 类型

cpp 复制代码
class Monster {
public:
    int level;
    std::string effect;      // 替代 char*
    std::vector<int> atks;   // 已很好
    // 无需手写拷贝构造/赋值/析构!编译器生成的即可正确深拷贝
};

✅ 使用 std::stringstd::vector 后,默认的拷贝行为就是深拷贝 ,且安全高效,符合 "零法则"(Rule of Zero)。


总结

  • 拷贝构造函数在初始化、传参、返回值时调用;
  • Monster 中因含 char*,必须手动实现深拷贝;
  • vector<int> 成员无需额外处理;
  • 最佳方案:std::string 替代裸指针,避免手写拷贝逻辑。
相关推荐
XH华8 分钟前
C++语言第二章类和对象(下)
开发语言·c++
从零开始的代码生活_18 分钟前
C++ stack、queue 与 priority_queue:容器适配器原理与实战
开发语言·c++·后端·学习·算法
techdashen40 分钟前
Go 1.25 新增 `reflect.TypeAssert`:更直接、更高效地从 `reflect.Value` 取出类型值
开发语言·后端·golang
2zcode42 分钟前
项目文档:基于MATLAB低采样率ISAR成像的快速稀疏重建算法研究
开发语言·算法·matlab
xixi09241 小时前
JMeter5.6.3基础使用教程1
java·开发语言
এ慕ོ冬℘゜1 小时前
原生 JS 实现自定义单选日历组件(无 UI 库、纯手写、禁用过去日期)
开发语言·javascript·ui
Ulyanov1 小时前
刚体动力学方程——牛顿-欧拉与陀螺效应的奥秘
开发语言·python·目标跟踪·雷达电子对抗·导引头
罗超驿1 小时前
15.Java异常处理:从束手无策到从容应对
java·开发语言
MoonBit月兔2 小时前
MoonBit v0.10.4版本更新
开发语言·人工智能·编程·moonbit
小樱花的樱花2 小时前
Linux 多线程编程:互斥锁(Mutex)详解
linux·c语言·开发语言