C++拷贝构造函数详解:浅拷贝与深拷贝的区别
C++中的拷贝构造函数用于根据现有对象创建新对象。如果对象包含指针或动态分配的内存,必须通过手动实现深拷贝来避免内存重复释放的错误;否则,编译器生成的默认浅拷贝将导致程序崩溃或数据损坏。
一般情况下的拷贝构造函数
拷贝构造函数的核心作用是将一个类的对象数据复制到另一个同类型的对象中。对于只包含基础数据类型(如 int, double, char)的类,开发者通常需要自行定义拷贝构造函数以实现成员逐一复制。
class A
{
private:
int n;
double d;
char s;
public:
A(const A& a);
};
A::A(const A& a)
{
this->n = a.n;
this->d = a.d;
this->s = a.s;
}
在上述定义中,传入的参数必须是类的引用(const A& a)。这一点至关重要,因为如果参数按值传递,系统在调用构造函数前需要先对实参进行拷贝,这又会触发拷贝构造函数的调用,从而陷入无限递归调用的死循环,最终导致栈溢出。
默认拷贝构造函数
当类中没有显式定义拷贝构造函数时,C++编译器会隐式生成一个默认的拷贝构造函数。该函数执行的是“逐成员”(member-wise)的赋值操作。
对于大多数不包含指针、文件句柄或系统资源的管理者来说,这种默认的浅拷贝行为是安全且有效的,它直接复制数据位的值。然而,一旦类内部涉及动态内存分配,这种简单的位复制就会埋下隐患。
浅拷贝和深拷贝
区分这两者的关键在于对象所管理的资源是否独立。
- 浅拷贝(Shallow Copy):仅复制数据成员的数值。如果成员是指针,则只复制指针地址,新旧对象将指向同一块堆内存。这是编译器默认的行为。
- 深拷贝(Deep Copy):不仅复制数据,还针对动态资源重新分配内存并复制内容。新旧对象各自拥有独立的资源,互不干扰。
以下是一个典型的因混淆拷贝方式而导致错误的场景:当一个类中包含指针类型成员时,若使用默认的浅拷贝,将在对象销毁时引发严重的内存问题。
错误演示:默认的浅拷贝
考虑以下类定义,它管理一段动态分配的字符串空间:
class A
{
private:
char* str;
int len;
public:
A(const char* s);
~A();
// 注意:此处未声明拷贝构造函数,使用默认浅拷贝
};
A::A(const char* s)
{
len = strlen(s);
str = new char[len+1];
strcpy(str, s);
cout << str << " object construct." << endl;
}
A::~A()
{
cout << str << " deleted." << endl;
delete[]str;
}
如果在主函数中进行如下拷贝操作:
int main(void)
{
A a1("Hello");
A a2 = a1; // 触发默认拷贝构造函数
return 0;
}
实际运行结果可能出现类似以下的报错或异常输出:
Hello object construct.
Hello deleted.
葺葺葺葺葺葺葺? deleted.
问题分析:在 A a2 = a1; 这一步,由于没有自定义拷贝构造函数,编译器执行了默认的浅拷贝。此时 a1.str 和 a2.str 指向堆上的同一块内存区域。
当程序退出 main 函数时,a2 先析构,释放了这块内存并将日志打印为 "Hello"。随后 a1 析构时,再次尝试释放同一片已经无效的内存,这在 C/C++ 中属于未定义行为,通常会导致程序崩溃或打印乱码。
正确做法:自定义深拷贝
为了避免上述资源竞争,必须手动重载拷贝构造函数,实现深拷贝逻辑:
class A
{
private:
char* str;
int len;
public:
A(const char* s);
~A();
A(const A& a); // 显式声明深拷贝构造函数
};
A::A(const char* s)
{
len = strlen(s);
str = new char[len+1];
strcpy(str, s);
cout << str << " object construct." << endl;
}
A::~A()
{
cout << str << " deleted." << endl;
delete[]str;
}
A::A(const A& a)
{
this->len = a.len;
str = new char[a.len+1]; // 关键:为新对象开辟独立内存
strcpy(str, a.str); // 关键:复制内容而非地址
}
在使用同样的测试代码后,正确的运行结果为:
Hello object construct.
Hello deleted.
Hello deleted.
在这个正确的版本中,拷贝构造函数为 a2 分配了新的内存空间并复制了 "Hello" 的内容。因此,两个对象的析构函数分别释放各自的内存,程序可以正常结束且无内存泄漏风险。
总结
拷贝构造函数是 C++ 类设计中不可或缺的一部分。虽然对于简单数据类型,编译器生成的默认拷贝构造函数足以应对,但在类涉及堆内存管理等资源分配时,遵循“规则三”(Rule of Three),即如果用户定义了析构函数、拷贝构造函数或拷贝赋值运算符中的任何一个,就需要全部定义它们,是防止内存错误的关键实践。
以上就是C++拷贝构造函数以及深浅拷贝的详细教程内容,更多关于C++面向对象编程的资料请关注本站其它相关文章!
本站声明
本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。













