浅拷贝仅复制指针值导致多对象共享同一内存,析构时可能引发重复释放和悬空指针;深拷贝通过自定义拷贝构造函数和赋值运算符为指针成员分配新内存并复制内容,确保对象独立性,避免内存错误。

在C++的内存管理中,理解浅拷贝和深拷贝是避免诸多内存错误的关键,简单来说,浅拷贝只是复制了对象成员的“值”,如果这些值是指针,那么新旧对象会共享同一块内存;而深拷贝则会为指针指向的资源也开辟新的内存空间,确保每个对象拥有独立的资源副本。
要实现C++中的浅拷贝和深拷贝,我们通常需要关注类的成员变量,尤其是那些指向动态分配内存的指针。
浅拷贝(Shallow Copy)
C++的默认拷贝行为就是浅拷贝。当你没有为类定义拷贝构造函数或拷贝赋值运算符时,编译器会自动生成它们,这些默认生成的函数会逐个成员地复制(member-wise copy)。如果类中包含指向动态分配内存的指针,那么新对象和原对象的指针将指向同一块内存区域。
立即学习“C++免费学习笔记(深入)”;
考虑一个简单的例子:
class MyString {
public:
char* data;
MyString(const char* s) {
data = new char[strlen(s) + 1];
strcpy(data, s);
}
~MyString() {
delete[] data;
}
};
// ... 在main函数中
MyString s1("Hello");
MyString s2 = s1; // 默认拷贝构造,浅拷贝
// 此时 s1.data 和 s2.data 指向同一块内存这里的
s2 = s1
s1.data
s2.data
s1
s2
delete[] data
data
delete[]
深拷贝(Deep Copy)
为了解决浅拷贝带来的问题,我们需要实现深拷贝。深拷贝意味着当复制对象时,如果对象内部包含指向动态分配内存的指针,我们不仅复制指针本身,还要为指针指向的内容也分配新的内存,并将内容复制过去。这通常通过自定义拷贝构造函数和拷贝赋值运算符来完成。
#include <iostream>
#include <cstring> // For strlen and strcpy
class MyString {
public:
char* data;
size_t length;
// 构造函数
MyString(const char* s = "") {
length = strlen(s);
data = new char[length + 1];
strcpy(data, s);
std::cout << "Constructor called for: " << data << std::endl;
}
// 析构函数
~MyString() {
std::cout << "Destructor called for: " << data << std::endl;
delete[] data;
data = nullptr; // 避免悬空指针
}
// 拷贝构造函数 (深拷贝实现)
MyString(const MyString& other) {
length = other.length;
data = new char[length + 1]; // 分配新的内存
strcpy(data, other.data); // 复制内容
std::cout << "Deep Copy Constructor called for: " << data << std::endl;
}
// 拷贝赋值运算符 (深拷贝实现)
MyString& operator=(const MyString& other) {
if (this == &other) { // 处理自我赋值
return *this;
}
// 释放旧资源
delete[] data;
// 分配新资源并复制内容
length = other.length;
data = new char[length + 1];
strcpy(data, other.data);
std::cout << "Deep Copy Assignment Operator called for: " << data << std::endl;
return *this;
}
// 获取字符串内容
const char* c_str() const {
return data;
}
};
// 示例用法
// int main() {
// MyString s1("Hello, World!");
// MyString s2 = s1; // 调用拷贝构造函数
// MyString s3("C++");
// s3 = s1; // 调用拷贝赋值运算符
//
// std::cout << "s1: " << s1.c_str() << std::endl;
// std::cout << "s2: " << s2.c_str() << std::endl;
// std::cout << "s3: " << s3.c_str() << std::endl;
//
// // 修改s1不会影响s2和s3,因为它们拥有独立的内存
// // (如果MyString有修改方法,这里可以展示)
//
// return 0;
// }在这个
MyString
MyString(const MyString& other)
operator=(const MyString& other)
data
other.data
MyString
C++默认的拷贝行为,也就是我们常说的浅拷贝,其核心问题在于它只复制了对象成员的“值”。对于那些基本类型(如
int
double
char*
int*
想象一下,你有一个
MyString
s1
data
s1
s2
MyString s2 = s1;
s2.data
s1.data
s2.data
s1.data
s1
s2
这种共享资源的方式会带来几个严重的后果:
s1
delete[] s1.data
s2
delete[] s2.data
s2.data
s1
s2.data
s2.data
s2.data
s1
data
s2
所以,默认的浅拷贝行为对于管理动态内存的类来说,几乎总是一个陷阱。它假定所有成员都是独立的,但指针成员的“值”只是一个地址,真正的资源在地址后面,这才是需要独立复制的。
正确实现深拷贝是C++中一个基础但又极其重要的技能,它确保了对象之间的数据独立性。这通常涉及到“三/五/零法则”(Rule of Three/Five/Zero),即如果一个类需要自定义析构函数、拷贝构造函数或拷贝赋值运算符中的任何一个,那么它很可能需要自定义所有这三个(或更多,考虑到C++11的移动语义)。
核心步骤:
析构函数 (~ClassName()
delete[] data;
data
nullptr
拷贝构造函数 (ClassName(const ClassName& other)
MyString s2 = s1;
MyString s2(s1);
other
other
other
MyString(const MyString& other) {
length = other.length;
data = new char[length + 1]; // 分配新内存
strcpy(data, other.data); // 复制内容
}拷贝赋值运算符 (ClassName& operator=(const ClassName& other)
s3 = s1;
this == &other
*this
other
other
**:允许链式赋值(
MyString& operator=(const MyString& other) {
if (this == &other) { // 自我赋值检查
return *this;
}
delete[] data; // 释放旧资源
length = other.length;
data = new char[length + 1]; // 分配新内存
strcpy(data, other.data); // 复制内容
return *this;
}注意事项:
new char[length + 1]
data
delete[]
// 采用 copy-and-swap idiom 实现拷贝赋值运算符
MyString& operator=(MyString other) { // 注意这里other是按值传递,会调用拷贝构造函数
swap(*this, other); // 交换资源
return *this;
}
// 还需要一个友元函数或成员函数来执行交换
friend void swap(MyString& first, MyString& second) {
using std::swap; // 允许ADL查找std::swap
swap(first.data, second.data);
swap(first.length, second.length);
}这种方式在
other
std::vector
std::string
虽然深拷贝在处理动态内存时至关重要,但浅拷贝并非一无是处。在某些特定场景下,浅拷贝不仅安全,而且是更高效或更符合逻辑的选择。关键在于理解“所有权”的概念。
当类不包含任何动态分配的资源时:
int
double
bool
std::string
std::vector
struct Point {
int x;
int y;
};
Point p1 = {10, 20};
Point p2 = p1; // 浅拷贝,但因为没有动态资源,等同于深拷贝当对象不“拥有”其指向的资源,而是作为“视图”或“引用”存在时:
StringView
char
new
delete
class StringView {
public:
const char* str;
size_t len;
StringView(const char* s, size_t l) : str(s), len(l) {}
// 默认拷贝构造和赋值运算符就是浅拷贝,且是正确的
// 因为StringView不拥有str指向的内存,不负责释放
};性能优化(极少数情况):
总结来说,浅拷贝是安全的,并且在不涉及动态内存管理或仅作为资源引用/视图时是正确的选择。它的风险主要在于,当类中存在指向堆内存的指针,并且这些指针代表了“所有权”时,默认的浅拷贝会破坏这种所有权模型,导致内存泄漏或重复释放。因此,关键在于明确你的类对资源是“拥有”还是“引用”。
以上就是C++内存管理基础中浅拷贝和深拷贝的实现方法的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号