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

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


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

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

  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 替代裸指针,避免手写拷贝逻辑。
相关推荐
zore_c1 小时前
【C语言】文件操作详解3(文件的随机读写和其他补充)
c语言·开发语言·数据结构·笔记·算法
CoderYanger1 小时前
动态规划算法-简单多状态dp问题:18.买卖股票的最佳时机Ⅳ
开发语言·算法·leetcode·动态规划·1024程序员节
Less is moree1 小时前
2.C语言文件操作(一):fgetc(),fgets(),fread的区别
c语言·开发语言·算法
CoderYanger1 小时前
动态规划算法-简单多状态dp问题:13.删除并获得点数
java·开发语言·数据结构·算法·leetcode·动态规划·1024程序员节
ohnoooo91 小时前
C++ STL库常用容器函数
java·开发语言
天骄t1 小时前
深入解析栈:数据结构与系统栈
java·开发语言·数据结构
源代码•宸1 小时前
GoLang并发示例代码1(关于逻辑处理器运行顺序)
开发语言·经验分享·后端·golang
曦樂~1 小时前
【C++11】引用折叠原理
开发语言·c++
松涛和鸣1 小时前
24、数据结构核心:队列与栈的原理、实现与应用
c语言·开发语言·数据结构·学习·算法