浅拷贝 vs 深拷贝 (C++)
浅拷贝 vs 深拷贝浅拷贝和深拷贝是 C 中关于对象拷贝的两个核心概念直接关系到内存安全、资源管理和程序稳定性。 一、浅拷贝Shallow Copy定义浅拷贝只是简单地复制对象的所有成员变量的值对于指针类型只复制指针本身不复制指针指向的数据。默认行为如果类没有自定义拷贝构造函数和赋值运算符编译器会自动生成一个默认拷贝构造函数和默认赋值运算符它们执行的就是浅拷贝。示例代码cppclass Shallow { public: int* data; int size; Shallow(int s) : size(s) { data new int[size]; for (int i 0; i size; i) { data[i] i; } } // 使用编译器默认生成的拷贝构造函数浅拷贝 // Shallow(const Shallow other) default; // 默认行为 }; int main() { Shallow obj1(5); Shallow obj2 obj1; // 浅拷贝 // obj1.data 和 obj2.data 指向同一块内存 obj1.data[0] 100; std::cout obj2.data[0]; // 输出 100互相影响 // 析构时同一块内存被释放两次 → 崩溃 return 0; }问题示意图text原始对象 (obj1) 拷贝对象 (obj2) ┌─────────────┐ ┌─────────────┐ │ data: 0x100 │ ────→ │ data: 0x100 │ ────→ 同一块堆内存 │ size: 5 │ │ size: 5 │ └─────────────┘ └─────────────┘ ↑ ↑ └─────── 共享 ──────────┘核心问题✅ 成员变量size被正确复制❌ 指针data只复制了地址值两个对象指向同一块内存❌ 修改一个对象的数据会影响另一个数据竞争❌ 析构时重复释放double free→程序崩溃 二、深拷贝Deep Copy定义深拷贝不仅复制对象本身还会递归复制指针所指向的动态分配内存为新对象分配独立的内存空间。示例代码cppclass Deep { public: int* data; int size; Deep(int s) : size(s) { data new int[size]; for (int i 0; i size; i) { data[i] i; } } // 自定义拷贝构造函数深拷贝 Deep(const Deep other) : size(other.size) { data new int[size]; // 分配新内存 for (int i 0; i size; i) { data[i] other.data[i]; // 复制数据 } } // 自定义赋值运算符深拷贝 Deep operator(const Deep other) { if (this ! other) { // 防止自赋值 delete[] data; // 释放旧内存 size other.size; data new int[size]; // 分配新内存 for (int i 0; i size; i) { data[i] other.data[i]; } } return *this; } ~Deep() { delete[] data; // 每个对象释放自己的内存 } }; int main() { Deep obj1(5); Deep obj2 obj1; // 深拷贝 // obj1.data 和 obj2.data 指向不同的内存块 obj1.data[0] 100; std::cout obj2.data[0]; // 输出 0不受影响 // 析构时各自释放自己的内存 → 安全 return 0; }示意图text原始对象 (obj1) 拷贝对象 (obj2) ┌─────────────┐ ┌─────────────┐ │ data: 0x100 │ ────→ │ data: 0x200 │ ────→ 独立堆内存 │ size: 5 │ │ size: 5 │ └─────────────┘ └─────────────┘ ↓ ↓ [0,1,2,3,4] [0,1,2,3,4] 完全独立 三、对比表格对比项浅拷贝深拷贝执行方式复制成员变量的值复制成员变量 递归复制指针指向的数据内存分配不分配新内存为指针分配新的独立内存指针处理只复制地址复制指针指向的内容是否独立共享资源不独立完全独立默认实现编译器默认生成需要自定义性能开销低只需复制值高需要分配内存和复制数据安全性❌ 危险double free✅ 安全适用场景无指针/资源管理的简单对象包含指针/资源的复杂对象 四、游戏服务器中的实际场景场景 1玩家对象必须深拷贝cppclass Player { public: std::string name; int* scores; // 动态数组 int scoreCount; // 必须实现深拷贝 Player(const Player other) : name(other.name), scoreCount(other.scoreCount) { scores new int[scoreCount]; std::copy(other.scores, other.scores scoreCount, scores); } };场景 2网络数据包浅拷贝 引用计数cppclass Packet { public: char* data; int size; std::shared_ptrchar sharedData; // 使用智能指针自动管理 // 智能指针自动实现安全的浅拷贝引用计数 Packet(const Packet other) default; // shared_ptr 自动处理 };场景 3只读配置表浅拷贝即可cppclass Config { public: const int* configData; // 指向只读全局数据 int length; // 浅拷贝足够因为数据是只读的 Config(const Config other) default; };️ 五、如何避免手写深拷贝1. 使用 RAII 容器推荐⭐⭐⭐⭐⭐cppclass BetterPlayer { public: std::string name; std::vectorint scores; // 自动管理内存 // 不需要自定义拷贝vector 自动深拷贝 // BetterPlayer(const BetterPlayer) default; // 安全 };2. 使用智能指针cppclass Player { public: std::shared_ptrint[] scores; // 自动引用计数 int scoreCount; // 默认拷贝安全共享所有权自动管理 Player(const Player) default; };3. 禁止拷贝cppclass NonCopyable { public: NonCopyable(const NonCopyable) delete; NonCopyable operator(const NonCopyable) delete; }; 六、面试高频考点Q1: 什么时候需要深拷贝A: 当类包含动态分配的内存、文件句柄、网络连接等资源时必须实现深拷贝。Q2: 为什么不推荐手动实现深拷贝A:容易出错自赋值、异常安全代码冗长推荐使用 RAII 容器如std::vector替代原始指针Q3: 移动语义和深拷贝的区别A:深拷贝复制资源资源翻倍移动语义转移资源所有权零拷贝cppPlayer(Player other) noexcept : scores(other.scores), scoreCount(other.scoreCount) { other.scores nullptr; // 窃取资源 }Q4: 什么是拷贝赋值运算符的自赋值问题cppPlayer operator(const Player other) { if (this other) return *this; // 必须检查 // ... 否则delete[] data; 再复制自己 → 崩溃 } 七、最佳实践总结实践说明优先使用 RAII使用std::vector、std::string、智能指针遵守三五法则需要析构 → 需要拷贝构造和赋值禁用拷贝如果不需要拷贝显式 delete使用移动语义减少不必要的深拷贝开销检查自赋值赋值运算符必须检查this ! other 八、面试回答模板浅拷贝只复制对象的值对于指针只复制地址导致多个对象共享同一块内存容易引发 double free。深拷贝则会为新对象分配独立的内存复制指针指向的数据。在游戏服务器开发中玩家数据、场景对象等包含动态资源的类必须实现深拷贝。但更好的做法是使用 RAII 容器如std::vector或智能指针避免手动管理内存。根据 C 的三五法则如果类需要自定义析构函数通常也需要自定义拷贝构造和拷贝赋值。现代 C 中我们更推荐使用移动语义来优化性能避免不必要的深拷贝。