一、深拷贝&浅拷贝
深拷贝与浅拷贝的根本区别,不在于复制了多少字节,在于拷贝后,两个对象是否共享了同一个底层资源
-
浅拷贝:仅仅逐字节复制对象本身的内存布局,两个对象中的指针成员指向同一块动态内存。
-
深拷贝:递归复制指针所指向的全部底层数据,为副本独立分配一块新的动态内存。
二、代码解析与示意图
1、浅拷贝,编译器默认行为
原始对象 s1 (栈) 堆内存 (0x1000)
┌─────────────┐ ┌─────────────┐
│ char* m_ptr │──────│ H E L L O \0│
│ int m_len=5 │ └─────────────┘
└─────────────┘
拷贝对象 s2 (栈)
┌─────────────┐
│ char* m_ptr │──────(指向同一块 0x1000) ❌ 共享了!
│ int m_len=5 │
└─────────────┘
2、深拷贝,手动处理
原始对象 s1 (栈) 堆内存 (0x1000)
┌─────────────┐ ┌─────────────┐
│ char* m_ptr │──────│ H E L L O \0│
│ int m_len=5 │ └─────────────┘
└─────────────┘
拷贝对象 s2 (栈) 堆内存 (0x2000) ✅ 独立新家!
┌─────────────┐ ┌─────────────┐
│ char* m_ptr │──────│ H E L L O \0│
│ int m_len=5 │ └─────────────┘
└─────────────┘
3、代码演示
#include <cstring>
#include <iostream>
class MyString {
private:
char* m_data; // 指向堆内存的指针
size_t m_len;
public:
// ---------- 构造函数 ----------
MyString(const char* str = "")
: m_len(strlen(str)), m_data(new char[m_len + 1]) {
strcpy(m_data, str);
std::cout << "构造: " << m_data << std::endl;
}
// ---------- 析构函数 ----------
~MyString() {
delete[] m_data; // 释放堆内存
std::cout << "析构: 释放内存" << std::endl;
}
// ==========================================
// ❌ 浅拷贝(编译器默认生成,如果不禁用会出大问题)
// ==========================================
// 默认拷贝构造:MyString(const MyString& other)
// 默认拷贝赋值:MyString& operator=(const MyString& other)
// 它们会执行:m_data = other.m_data; (逐字节复制指针值)
// 后果:两个对象指向同一块内存,析构时双重释放(Double Free)-> 崩溃!
// ==========================================
// ✅ 深拷贝(必须手动实现)
// ==========================================
// 1. 拷贝构造函数(深拷贝)
MyString(const MyString& other)
: m_len(other.m_len), m_data(new char[m_len + 1]) {
strcpy(m_data, other.m_data); // 复制数据到新内存
std::cout << "深拷贝构造: " << m_data << std::endl;
}
// 2. 拷贝赋值运算符(深拷贝 + 自我赋值检测)
MyString& operator=(const MyString& other) {
std::cout << "深拷贝赋值: " << other.m_data << std::endl;
if (this == &other) { // 防止自我赋值:s = s;
return *this;
}
// 步骤1:释放旧资源
delete[] m_data;
// 步骤2:分配新内存并复制数据
m_len = other.m_len;
m_data = new char[m_len + 1];
strcpy(m_data, other.m_data);
return *this;
}
// 获取数据(打印用)
const char* c_str() const { return m_data; }
};
// ========== 测试代码 ==========
int main() {
MyString s1("Hello");
MyString s2(s1); // 调用深拷贝构造(独立内存)
MyString s3("World");
s3 = s1; // 调用深拷贝赋值(s3 释放旧内存,申请新内存)
std::cout << "s1: " << s1.c_str() << std::endl;
std::cout << "s2: " << s2.c_str() << std::endl;
std::cout << "s3: " << s3.c_str() << std::endl;
return 0; // 三个对象析构,各自释放各自的内存,安全!
}
三、工程实战原则
1、三/五法则:
-
如果一个类需要自定义析构函数释放资源,那么一定需要自定义拷贝构造函数和拷贝赋值运算符,以及C11后的移动构造和移动赋值
-
反之,如果不需要析构函数,就不要自定义拷贝操作,直接让编译器生成默认的浅拷贝。
2、在业务代码中,使用string替换char*,用vector替换动态数组
-
因为他们内部已实现了深拷贝。
四、C11中的移动语义
-
C++中的移动语义既不是深拷贝,也不是浅拷贝,只是一种资源窃取
解析:
-
浅拷贝会导致两个对象共享资源,存在重复释放资源危险
-
深拷贝会复制一份数据(昂贵)
-
移动是把源对象的指针直接置空(nullptr),并把指针值偷给目标对象,移动后的源对象处于有效但未指定的状态,他的析构函数不会再释放资源。

1265

被折叠的 条评论
为什么被折叠?



