当前位置: 首页 > news >正文

C++ 拷贝构造函数 浅拷贝 深拷贝

C++ 的拷贝构造函数(Copy Constructor)是一种特殊的构造函数,用于通过已有对象初始化新创建的对象。它在对象复制场景中起关键作用,尤其在涉及动态内存管理时需特别注意深浅拷贝问题。

一、定义与语法​​

拷贝构造函数的​​参数为本类对象的常量引用​​,格式为 ClassName(const ClassName &src)。若未显式定义,​​编译器会自动生成默认拷贝构造函数​​,执行​​浅拷贝​​(逐成员复制值)。

二、调用时机​​

拷贝构造函数在以下场景被调用:

  • 对象初始化​​:通过已有对象显式或隐式创建新对象(如 Student b = a; 或 Student b(a);)。
  • ​​值传递参数​​:对象以值传递方式传入函数。
  • 函数返回值​​:对象以值传递方式从函数返回。
  • 异常处理​​:抛出或捕获异常对象时可能触发。

三、深浅拷贝问题​

1. ​​浅拷贝的风险​​

默认拷贝构造函数仅复制成员的值。若类包含​​指针成员​​,浅拷贝会导致新对象与原对象指向同一内存地址,析构时引发重复释放错误。

2. ​​深拷贝的实现​​

需​​显式定义拷贝构造函数​​,为指针成员分配独立内存并复制内容。

四、与赋值运算符的区别​​

  • 拷贝构造函数​​:在​​对象创建时​​调用,用于初始化新对象。
  • 赋值运算符​​:在​​已存在对象​​间赋值时调用(如 a = b;),需重载 operator=。
  • 关键区别​​:
Student c = a; // 调用拷贝构造函数(对象c尚未存在)
c = b;         // 调用赋值运算符(对象c已存在)

五、最佳实践​​

  • 遵循三法则​​:若类需自定义拷贝构造函数、析构函数或赋值运算符之一,通常需同时实现三者。
  • 禁用拷贝​​:可通过 = delete 显式删除拷贝构造函数,禁止对象复制(如单例模式)。
  • 优化性能​​:优先使用 const & 传递对象参数,避免不必要的拷贝。

六、浅拷贝与深拷贝对比示例​

#include <iostream>
#include <cstring>
using namespace std;// 浅拷贝导致的问题
class ShallowString {
public:char* data;// 构造函数ShallowString(const char* s) {data = new char[strlen(s) + 1];strcpy(data, s);}// 默认拷贝构造函数(浅拷贝)~ShallowString() { delete[] data; }
};// 深拷贝解决方案
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() { delete[] data; }
};int main() {// 浅拷贝问题演示ShallowString a("Hello");ShallowString b = a;  // 默认浅拷贝cout << "浅拷贝结果: " << a.data << " | " << b.data << endl;// 运行时崩溃:a和b析构时重复释放同一内存// 深拷贝解决方案DeepString c("World");DeepString d = c;      // 调用深拷贝构造函数strcpy(c.data, "C++"); // 修改原对象不影响副本cout << "深拷贝结果: " << c.data << " | " << d.data << endl;return 0;
}

输出​​(浅拷贝运行崩溃,深拷贝输出):

浅拷贝结果: Hello | Hello
深拷贝结果: C++ | World

​​关键点​​:

  • 浅拷贝类未定义拷贝构造函数,导致重复释放内存
  • 深拷贝类显式分配独立内存,确保对象独立性

七、拷贝构造函数调用时机示例​

#include <iostream>
using namespace std;class Logger {
public:int id;// 构造函数Logger(int x) : id(x) { cout << "构造对象" << id << endl; }// 拷贝构造函数Logger(const Logger& src) : id(src.id + 100) {cout << "拷贝构造对象" << id << endl;}
};// 值传递触发拷贝构造
void process(Logger obj) {cout << "处理对象" << obj.id << endl;
}// 返回值触发拷贝构造
Logger create() {Logger temp(999);return temp;  // 触发拷贝构造(编译器可能优化)
}int main() {Logger a(1);                 // 调用普通构造函数Logger b = a;               // 调用拷贝构造函数[3](@ref)process(a);                  // 值传递触发拷贝构造[4](@ref)Logger c = create();         // 返回值触发拷贝构造return 0;
}
构造对象1
拷贝构造对象101
拷贝构造对象101
处理对象101
构造对象999
拷贝构造对象1099
Logger b = a 显式初始化触发拷贝
process(a) 值传递参数触发拷贝
返回值时编译器可能优化(RVO),但逻辑上仍存在拷贝过程

八、完整深拷贝类示例(含赋值运算符)

#include <iostream>
#include <cstring>
using namespace std;class Vector {
private:int* arr;size_t size;
public:// 构造函数Vector(size_t s) : size(s) {arr = new int[size]{0};cout << "构造Vector(大小" << size << ")" << endl;}// 深拷贝构造函数Vector(const Vector& src) : size(src.size) {arr = new int[size];memcpy(arr, src.arr, size * sizeof(int));cout << "深拷贝Vector" << endl;}// 赋值运算符重载Vector& operator=(const Vector& rhs) {if (this != &rhs) {delete[] arr;          // 释放原有资源size = rhs.size;arr = new int[size];memcpy(arr, rhs.arr, size * sizeof(int));cout << "赋值运算符调用" << endl;}return *this;}// 析构函数~Vector() { delete[] arr; cout << "销毁Vector" << endl;}void set(int index, int value) { arr[index] = value; }void print() {cout << "[ ";for (size_t i = 0; i < size; ++i)cout << arr[i] << " ";cout << "]" << endl;}
};int main() {Vector v1(3);v1.set(0, 10);v1.set(1, 20);v1.set(2, 30);Vector v2 = v1;        // 调用拷贝构造函数v2.set(1, 99);         // 修改副本不影响原对象Vector v3(2);v3 = v1;               // 调用赋值运算符cout << "v1: ";v1.print();  // [10 20 30]cout << "v2: ";v2.print();  // [10 99 30]cout << "v3: ";v3.print();  // [10 20 30]return 0;
}
构造Vector(大小3)
深拷贝Vector
构造Vector(大小2)
赋值运算符调用
v1: [ 10 20 30 ]
v2: [ 10 99 30 ]
v3: [ 10 20 30 ]
销毁Vector
销毁Vector
销毁Vector
设计原则​​:
  1. 遵循三法则:同时实现拷贝构造、析构和赋值运算符
  2. 自赋值检查:if (this != &rhs) 防止资源错误释放
  3. 深拷贝保证:memcpy复制数据而非指针地址

九、禁用拷贝的示例​

class NonCopyable {
public:NonCopyable() = default;// 删除拷贝构造函数和赋值运算符NonCopyable(const NonCopyable&) = delete;NonCopyable& operator=(const NonCopyable&) = delete;
};int main() {NonCopyable obj1;// NonCopyable obj2 = obj1;  // 编译错误// obj1 = NonCopyable();     // 编译错误return 0;
}

应用场景​​:单例模式、资源独占类等需要禁止复制的场景

十、浅拷贝何深拷贝的本质区别是什么

浅拷贝仅复制指针地址,深拷贝递归复制所有数据到独立内存

十一、使用智能指针避免手动管理

#include <iostream>
#include <memory>
using namespace std;class SafeString {
public:shared_ptr<char[]> data;  // 共享指针自动管理内存// 构造函数SafeString(const char* s) {data = make_shared<char[]>(strlen(s) + 1);strcpy(data.get(), s);cout << "构造 SafeString: " << data.get() << endl;}// 无需定义拷贝构造函数和析构函数!
};int main() {SafeString s1("Modern C++");SafeString s2 = s1;  // 共享指针引用计数+1strcpy(s1.data.get(), "Updated");cout << "s1.data: " << s1.data.get() << endl;cout << "s2.data: " << s2.data.get() << endl;return 0;  // 自动释放内存,无崩溃
}
构造 SafeString: Modern C++
s1.data: Updated
s2.data: Updated  // 共享同一内存但无双重释放风险
关键点​​:
  • 使用 shared_ptr 自动管理内存生命周期
  • 适用于资源共享场景,无需手动实现深拷贝
注意:

虽然使用了shared_ptr,但是SafeString s2 = s1依然是浅拷贝,只是不报错罢了。

相关文章:

  • 【以太网安全】——防护高级特性配置总结
  • Windows7升级Windows10,无法在此驱动器上安装Windows
  • 鸿道操作系统Type 1虚拟化:破局AI机器人与智能汽车的“安全”与“算力”双刃剑
  • STM32提高篇: WIFI通讯
  • S32K144学习(16)-Bootloader
  • EdgeGPT - 新版Bing聊天功能逆向工程
  • 序列决策问题(Sequential Decision-Making Problem)
  • (区间 dp)洛谷 P6879 JOI2020 Collecting Stamps 3 题解
  • docker镜像新增加用户+sudo权限,无dockerfile
  • AI领域:MCP 与 A2A 协议的关系
  • 网络威胁情报 | Friday Overtime Trooper
  • MCP使用SSE和STDIO模式时,mcp client 如何连接
  • 深度学习是什么?该怎么入门学习?
  • Unity 导出Excel表格
  • [特殊字符]fsutil命令用法详解
  • pcp补丁升级手顺
  • (8)ECMAScript语法详解
  • 入门-C编程基础部分:17、typedef
  • 创建redis-cluster集群
  • 提交到Gitee仓库
  • 用8年还原曹操墓鉴定过程,探寻曹操墓新书创作分享会举行
  • 分离19年后:陈杨梅首度露面,父亲亲手喂棉花糖给女儿吃
  • 沃尔沃中国公开赛夺冠,这是吴阿顺与上海的十年之约
  • 行拘!如此引流诱导违法犯罪不该被纵容
  • 摩根大通首席执行官:贸易战损害美国信誉
  • 习近平结束对越南、马来西亚和柬埔寨国事访问回到北京