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* effect 需 new 新内存并 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::string 和 std::vector 后,默认的拷贝行为就是深拷贝 ,且安全高效,符合 "零法则"(Rule of Zero)。


总结

  • 拷贝构造函数在初始化、传参、返回值时调用;
  • Monster 中因含 char*,必须手动实现深拷贝;
  • vector<int> 成员无需额外处理;
  • 最佳方案:用 std::string 替代裸指针,避免手写拷贝逻辑。
相关推荐
nice_lcj5202 小时前
ThreadLocal 入门:它是怎么隔离数据的、为什么会导致内存泄漏
java·开发语言·后端·面试·并发编程·内存泄漏·threadlocal
紧跟先前的步伐2 小时前
Go AI Agent 实战 28|Agent 的预算、超时与停止条件
开发语言·人工智能·后端·ai·golang
bksczm3 小时前
InnoDB 行格式详解:DYNAMIC / COMPACT / COMPRESSED / REDUNDANT
java·开发语言·数据库
春涧草茶4 小时前
慢就是快16-1协程
开发语言·前端·python
Dovis(誓平步青云)4 小时前
启元 T1 Pro 人机相声搭档:语音-触摸双通道的启元机器人节奏引擎设计
开发语言·前端·javascript·microsoft·交互
做科研的周师兄4 小时前
【R语言实战】MixSIAR 氢氧同位素水源解析全流程:从混合空间校验到贝叶斯 MCMC 收敛诊断(后附完整代码)
java·大数据·开发语言
闻缺陷则喜何志丹5 小时前
【C++特性】四种类型转
c++·算法·类型转换
.痞老板.6 小时前
java ee--计算机是如何工作的
java·开发语言·java-ee
奇树谦6 小时前
Qt Quick 图片加载与 GPU 纹理机制详解:QImage、QQuickTextureFactory、QSGTexture 与异步加载
开发语言·qt·系统架构
青梅味猪大肠6 小时前
【操作系统-60】临近适应算法
java·linux·开发语言·嵌入式硬件·算法·操作系统