目录
前言
在 C++ 面向对象编程中,拷贝构造函数是构造函数的一种特殊形式,用于通过已有对象初始化新对象。同时,当类中包含指针成员时,还涉及深拷贝与浅拷贝的区别。理解这些概念是掌握 C++ 对象生命周期和内存管理的关键。本文将详细介绍一下拷贝构造
概念介绍
拷贝构造函数
拷贝构造函数是一种特殊的构造函数,其作用是用一个已存在的对象初始化另一个新对象。它的声明格式为:
类名(const 类名& 引用对象);
需要注意的是,参数必须是引用类型(否则会引发 “拷贝构造的无限递归” 问题),且通常被声明为 const 以保证原对象不被修改。
拷贝构造的调用场景
C++ 中拷贝构造函数的调用主要有以下三种场景:
1、用已有对象初始化新对象:例如 Person p2 = p1; 或 Person p3(p1);。
2、值传递给函数参数:当函数参数以 “值传递” 形式接收对象时,会调用拷贝构造初始化形参。(这里是为什么拷贝构造的参数必须是引用的原因了,下面会具体介绍)
3、以值形式返回局部对象:函数返回局部对象时,会调用拷贝构造生成一个匿名对象(现代编译器可能通过 RVO 优化省略此过程)。
浅拷贝与深拷贝
浅拷贝:默认的拷贝行为,仅对成员变量进行 “值拷贝”。若类中包含指针成员,浅拷贝会导致多个对象共享同一块堆内存,析构时重复释放内存引发错误(重复释放同一块内存,导致程序崩溃)。
深拷贝:手动实现的拷贝逻辑,对指针成员会重新申请堆内存并复制值,保证每个对象拥有独立的资源,避免内存问题。
深浅拷贝的选择
当类中无指针 / 动态内存资源时,浅拷贝足够(如仅包含 int、double 等基本类型成员)。
当类中有指针 / 动态内存资源时(如 new 申请的堆内存),必须实现深拷贝,确保每个对象的资源独立管理。
拷贝构造常见问题
问题 1:拷贝构造的无限递归
场景:若拷贝构造函数的参数不是引用类型,而是值传递,会引发无限递归。
class Person {
public:
// 错误的拷贝构造(参数为值传递)
Person(Person other) {
cout << "拷贝构造" << endl;
}
};
问题原因:调用 Person(Person other) 时,实参传递给形参 other 会触发拷贝构造,而拷贝构造的调用又需要传递实参,从而陷入无限递归,最终导致栈溢出。
解决方法:将拷贝构造的参数改为引用类型,且通常为 const 引用,如 Person(const Person& other)。
问题 2:浅拷贝导致的重复内存释放
场景:类中包含指针成员,使用编译器默认的浅拷贝时,多个对象共享同一块堆内存,析构时重复释放引发程序崩溃。
class A {
int* num;
public:
A(int val) {
num = new int(val); // 申请堆内存
}
// 未实现深拷贝(依赖编译器默认浅拷贝)
~A() {
delete num; // 释放堆内存
}
};
int main() {
A a(3);
A b = a; // 浅拷贝,a.num和b.num指向同一块堆内存
return 0; // 析构时a和b会重复释放num指向的内存,程序崩溃
}
问题原因:浅拷贝仅复制指针的值(地址),导致 a.num 和 b.num 指向同一块堆内存。析构时 a 先释放内存,b 析构时再次释放已释放的内存,引发 “双重释放” 错误。
解决方法:手动实现深拷贝,在拷贝构造中为新对象重新申请堆内存并复制值,保证每个对象的指针指向独立的内存区域。
class A {
int* num;
public:
// 有参构造:为当前对象申请堆内存
A(int val) {
num = new int(val); // 申请堆内存并初始化
cout << "有参构造:分配内存地址 " << num << endl;
}
// 深拷贝构造函数:为新对象独立申请堆内存
A(const A& other) {
// 复制原对象指针指向的值(而非地址)
num = new int(*(other.num));
cout << "深拷贝构造:新内存地址 " << num << "(复制自 " << other.num << ")" << endl;
}
// 析构函数:释放当前对象的堆内存
~A() {
cout << "析构函数:释放内存地址 " << num << endl;
delete num; // 安全释放当前对象独有的堆内存
}
};
int main() {
A a(3); // 调用有参构造,分配堆内存
A b = a; // 调用深拷贝构造,为b分配独立堆内存(值与a相同)
return 0; // 析构时a和b释放各自的堆内存,无重复释放问题
}
拷贝构造的实现与调用
#include<iostream>
using namespace std;
class Person {
public:
// 无参构造
Person() {
cout << "无参构造" << endl;
}
// 有参构造
Person(int age) {
cout << "有参构造" << endl;
}
// 拷贝构造(参数为const引用,避免递归调用)
Person(const Person &other) {
cout << "拷贝构造" << endl;
}
// 析构函数
~Person() {
cout << "析构函数" << endl;
}
};
// 场景1:值传递给函数参数
void fun(Person sum) {
// 形参sum由实参通过拷贝构造初始化
}
// 场景2:以值形式返回局部对象
Person fun2() {
Person p;
return p; // 理论上调用拷贝构造,现代编译器可能优化(RVO)
}
int main() {
// 场景1:用已有对象初始化新对象
Person p1;
Person p2 = p1; // 隐式调用拷贝构造
Person p3(p1); // 显式调用拷贝构造
// 场景1:函数值传递
fun(p1);
// 场景2:函数值返回
fun2();
// 匿名对象(仅当前行有效)
Person();
return 0;
}
总结
拷贝构造函数是 C++ 中对象初始化的重要机制,需注意其调用场景和 “引用参数” 的设计,避免无限递归。
浅拷贝适用于无动态资源的类,深拷贝是包含指针 / 堆内存类的必选,否则会引发内存泄漏或重复释放的严重问题。
理解深浅拷贝的区别,是掌握 C++ 内存管理和对象生命周期的核心环节,在实际开发中需根据类的成员特性选择合适的拷贝策略,同时警惕拷贝构造的常见问题(如无限递归、重复释放内存)并采用对应解决方法。
1092

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



