目录
[1.1 为什么需要对象复制?](#1.1 为什么需要对象复制?)
[1.2 默认复制行为的问题](#1.2 默认复制行为的问题)
[2.1 定义与作用](#2.1 定义与作用)
[2.2 与拷贝赋值运算符的区别](#2.2 与拷贝赋值运算符的区别)
[3.1 对象初始化](#3.1 对象初始化)
[3.2 函数参数传递](#3.2 函数参数传递)
[3.3 函数返回对象](#3.3 函数返回对象)
[3.4 容器元素操作](#3.4 容器元素操作)
[4.1 编译器自动生成的默认行为](#4.1 编译器自动生成的默认行为)
[4.2 浅拷贝引发的典型问题](#4.2 浅拷贝引发的典型问题)
[5.1 深拷贝的实现原则](#5.1 深拷贝的实现原则)
[5.2 深拷贝代码示例](#5.2 深拷贝代码示例)
[6.1 防止对象复制:删除复制构造函数](#6.1 防止对象复制:删除复制构造函数)
[6.2 移动语义与复制构造函数](#6.2 移动语义与复制构造函数)
[6.3 复制构造函数与 const 关键字](#6.3 复制构造函数与 const 关键字)
[7.1 陷阱一:遗漏复制构造函数导致资源泄漏](#7.1 陷阱一:遗漏复制构造函数导致资源泄漏)
[7.2 陷阱二:复制构造函数中未正确初始化成员](#7.2 陷阱二:复制构造函数中未正确初始化成员)
[7.3 最佳实践:遵循 Rule of Three/Five](#7.3 最佳实践:遵循 Rule of Three/Five)
在 C++ 的面向对象编程中,对象的复制是一个核心操作。复制构造函数作为类的特殊成员函数,负责从一个已存在的对象创建新对象,确保资源管理的正确性和数据的一致性。
一、对象复制的本质需求
1.1 为什么需要对象复制?
在C++程序设计中,对象复制是面向对象编程的核心操作之一。典型应用场景包括:
-
函数参数的值传递
-
返回局部对象
-
容器元素操作
-
对象初始化
cpp
void processObject(MyClass obj); // 值传递触发拷贝
MyClass createObject(); // 返回对象触发拷贝
int main() {
MyClass a;
MyClass b = a; // 初始化拷贝
vector<MyClass> vec;
vec.push_back(a); // 容器存储拷贝
}
1.2 默认复制行为的问题
编译器生成的默认拷贝构造函数执行浅拷贝(Shallow Copy),对于包含指针成员的类会引发严重问题:
cpp
class ShallowArray {
int* data;
size_t size;
public:
ShallowArray(size_t n) : size(n), data(new int[n]) {}
~ShallowArray() { delete[] data; }
};
int main() {
ShallowArray arr1(5);
ShallowArray arr2 = arr1; // 双重释放崩溃!
}
二、复制构造函数的基本概念
2.1 定义与作用
复制构造函数 是一种特殊的构造函数,其函数名与类名相同,没有返回类型,参数为当前类的常量引用 (const ClassName&
)。它的作用是:
- 从已存在的对象创建新对象,实现对象的深拷贝(Deep Copy)。
- 确保在对象复制过程中,资源(如动态内存、文件句柄等)被正确复制,避免浅拷贝(Shallow Copy)导致的悬挂指针或资源泄漏。
语法格式:
cpp
class ClassName {
public:
ClassName(const ClassName& obj); // 复制构造函数声明
};
// 定义示例
ClassName::ClassName(const ClassName& obj) {
// 实现对象复制逻辑
}
2.2 与拷贝赋值运算符的区别
特性 | 复制构造函数 | 拷贝赋值运算符(operator=) |
---|---|---|
触发时机 | 对象创建时(初始化阶段) | 对象已存在时(赋值操作阶段) |
参数 | const ClassName& |
const ClassName& |
返回值 | 无(构造函数无返回值) | ClassName& (当前对象引用) |
核心功能 | 从无到有创建对象的复制版本 | 从已有对象向当前对象赋值 |
示例对比:
cpp
ClassName obj1; // 调用默认构造函数
ClassName obj2(obj1); // 调用复制构造函数(对象创建)
obj2 = obj1; // 调用拷贝赋值运算符(对象已存在)
三、复制构造函数的触发场景
3.1 对象初始化
当使用一个已存在的对象初始化新对象时,复制构造函数被触发:
cpp
#include <iostream> // 包含输入输出流头文件
using namespace std; // 使用标准命名空间
class Point {
public:
int x, y;
Point(int a, int b) : x(a), y(b) {}
Point(const Point& p) : x(p.x), y(p.y) { // 复制构造函数
cout << "Copy constructor called" << endl;
}
};
int main() {
Point p1(1, 2);
Point p2 = p1; // 等价于Point p2(p1),触发复制构造函数
return 0;
}

3.2 函数参数传递
当对象以值传递方式传入函数时,会触发复制构造函数(形参是实参的副本):
cpp
#include <iostream>
using namespace std;
// 定义Point类
class Point {
public:
int x, y; // 公有数据成员
// 构造函数
Point(int a, int b) : x(a), y(b) {}
// 复制构造函数
Point(const Point& p) : x(p.x), y(p.y) {
cout << "Copy constructor called: (" << x << "," << y << ")" << endl;
}
};
// 值传递函数,触发复制构造函数
void printPoint(Point p) {
cout << "Point value: " << p.x << "," << p.y << endl;
}
int main() {
Point p(3, 4); // 创建Point对象p
printPoint(p); // 调用printPoint,触发复制构造函数
return 0;
}

3.3 函数返回对象
当函数返回一个对象时,会通过复制构造函数创建临时对象作为返回值:
cpp
#include <iostream>
using namespace std;
class Point {
public:
int x, y;
// 构造函数
Point(int a = 0, int b = 0) : x(a), y(b) {
cout << "Constructor called: (" << x << "," << y << ")" << endl;
}
// 复制构造函数
Point(const Point& p) : x(p.x), y(p.y) {
cout << "Copy constructor called: (" << x << "," << y << ")" << endl;
}
// 移动构造函数(C++11,用于验证RVO与移动语义)
Point(Point&& p) noexcept : x(p.x), y(p.y) {
cout << "Move constructor called: (" << x << "," << y << ")" << endl;
p.x = p.y = 0; // 标记原对象已移动
}
};
Point getPoint() {
Point temp(5, 6); // 调用构造函数创建temp
cout << "Returning temp from getPoint" << endl;
return temp; // 返回对象,触发复制/移动构造或RVO
}
int main() {
cout << "Creating point p..." << endl;
Point p = getPoint(); // 接收返回值,观察构造函数调用
cout << "p.x = " << p.x << ", p.y = " << p.y << endl;
return 0;
}

3.4 容器元素操作
在容器(如vector
、list
)中插入对象时,可能触发复制构造函数:
cpp
#include <iostream>
#include <vector> // 引入vector容器头文件
using namespace std;
// 定义Point类
class Point {
public:
int x, y;
// 构造函数
Point(int a = 0, int b = 0) : x(a), y(b) {
cout << "Constructor called: (" << x << "," << y << ")" << endl;
}
// 复制构造函数(关键:确保容器复制元素时正确调用)
Point(const Point& p) : x(p.x), y(p.y) {
cout << "Copy constructor called: (" << x << "," << y << ")" << endl;
}
};
int main() {
vector<Point> vec; // 创建空vector
Point p(1, 2); // 调用构造函数创建对象p
cout << "Pushing p into vector..." << endl;
vec.push_back(p); // 向vector添加元素,触发复制构造函数
cout << "Vector size: " << vec.size() << endl;
return 0;
}

四、默认复制构造函数:浅拷贝的风险
4.1 编译器自动生成的默认行为
如果类中未显式定义复制构造函数,编译器会自动生成一个默认复制构造函数 ,执行浅拷贝(逐字节复制):
- 对于基本数据类型(如
int
、double
),浅拷贝是安全的。 - 对于指针成员,浅拷贝会导致多个对象指向同一内存地址,引发悬挂指针 或双重释放问题。
4.2 浅拷贝引发的典型问题
场景 :类中包含动态分配的内存(如int* data
),使用默认复制构造函数:
cpp
class BadExample {
public:
int* data;
BadExample(int size) {
data = new int[size]; // 分配内存
}
// 未定义复制构造函数,使用默认浅拷贝
};
int main() {
BadExample obj1(5);
BadExample obj2 = obj1; // 默认复制构造函数,浅拷贝data指针
obj1.data[0] = 10; // obj2.data[0]也会变为10(共享内存)
delete[] obj1.data; // 释放内存后,obj2.data成为悬挂指针
delete[] obj2.data; // 二次释放,程序崩溃
return 0;
}
问题根源 : 浅拷贝导致多个对象的指针成员指向同一内存块,释放后引发未定义行为。
五、自定义复制构造函数:实现深拷贝
5.1 深拷贝的实现原则
当类中包含资源管理成员 (如指针、文件句柄等)时,必须显式定义复制构造函数,实现深拷贝:
- 为新对象分配独立的资源。
- 将原对象的资源内容复制到新资源中。
- 确保原对象与新对象的资源相互独立,互不影响。
5.2 深拷贝代码示例
cpp
#include <iostream>
#include <algorithm> // 包含copy算法
using namespace std;
class GoodExample {
public:
int* data;
int size;
GoodExample(int s) : size(s) {
data = new int[size]; // 分配动态内存
fill(data, data + size, 0); // 使用fill初始化数组为0(替代手动循环)
}
// 自定义复制构造函数(深拷贝)
GoodExample(const GoodExample& obj) : size(obj.size) {
data = new int[size]; // 为新对象分配独立内存
copy(obj.data, obj.data + size, data); // 复制数据
cout << "Deep copy constructor called" << endl;
}
~GoodExample() {
delete[] data; // 释放动态内存
}
};
int main() {
GoodExample obj1(3); // 创建包含3个元素的对象
obj1.data[0] = 1; // 修改obj1的第一个元素为1
GoodExample obj2 = obj1; // 调用深拷贝构造函数,创建obj2
obj1.data[0] = 100; // 修改obj1的第一个元素为100
// 输出obj2的第一个元素(深拷贝后应保持原值0,因fill初始化时设为0,且未被obj2修改)
cout << "obj2.data[0] = " << obj2.data[0] << endl;
return 0;
}

六、复制构造函数的高级特性
6.1 防止对象复制:删除复制构造函数
通过将复制构造函数声明为delete
,可以禁止对象的复制(C++11 及以后):
cpp
class NonCopyable {
public:
NonCopyable() = default;
NonCopyable(const NonCopyable&) = delete; // 禁止复制构造
NonCopyable& operator=(const NonCopyable&) = delete; // 禁止拷贝赋值
};
int main() {
NonCopyable obj1;
NonCopyable obj2 = obj1; // 编译错误:复制构造函数被删除
return 0;
}
6.2 移动语义与复制构造函数
在 C++11 中,移动构造函数(Move Constructor
)与复制构造函数共同构成对象的复制 / 移动语义:
- 复制构造函数 :处理左值引用(已存在的对象),执行深拷贝。
- 移动构造函数 :处理右值引用(临时对象),转移资源所有权,避免深拷贝的开销。
示例:移动构造函数优化性能
cpp
#include <iostream>
// 定义 Resource 类
class Resource {
public:
int* data;
// 构造函数,分配指定大小的内存
Resource(int size) : data(new int[size]) {
std::cout << "Constructor: " << data << std::endl;
}
// 复制构造函数(深拷贝)
Resource(const Resource& rhs) : data(new int[rhs.data[0]]) {
std::cout << "Copy Constructor: " << data << " from " << rhs.data << std::endl;
}
// 移动构造函数(资源转移)
Resource(Resource&& rhs) noexcept : data(rhs.data) {
rhs.data = nullptr; // 原对象置空,避免双重释放
std::cout << "Move Constructor: " << data << " from " << rhs.data << std::endl;
}
// 析构函数,释放动态分配的内存
~Resource() {
delete[] data;
std::cout << "Destructor: " << data << std::endl;
}
};
// 返回一个 Resource 对象
Resource getResource() {
return Resource(10); // 返回右值,触发移动构造函数
}
int main() {
Resource obj = getResource(); // 优先调用移动构造函数
return 0;
}

6.3 复制构造函数与 const 关键字
复制构造函数的参数必须为常量引用 (const ClassName&
),原因如下:
- 允许接收临时对象 :临时对象是右值,只能绑定到
const
引用。 - 避免递归调用 :若参数为非
const
引用,复制构造函数内部创建临时对象时会再次触发复制构造,导致无限递归。
cpp
ClassName::ClassName(ClassName& obj) { // 错误:非const引用无法接收临时对象
// 编译错误:无法从const ClassName转换为ClassName&
}
七、常见陷阱与最佳实践
7.1 陷阱一:遗漏复制构造函数导致资源泄漏
场景:类中包含动态资源(如文件句柄、网络连接),未定义复制构造函数:
cpp
class FileHandler {
public:
FILE* file;
FileHandler(const char* path) {
file = fopen(path, "r"); // 打开文件
}
~FileHandler() {
fclose(file); // 关闭文件
}
};
int main() {
FileHandler f1("data.txt");
FileHandler f2 = f1; // 默认浅拷贝,f1和f2的file指针相同
// f1和f2析构时会两次关闭同一文件,导致程序崩溃
return 0;
}
解决方案:定义复制构造函数,实现资源的深拷贝或禁止复制。
7.2 陷阱二:复制构造函数中未正确初始化成员
错误示例:
cpp
class Vector {
public:
int* elements;
int length;
Vector(int len) : length(len) {
elements = new int[len];
}
Vector(const Vector& vec) { // 错误:未初始化length
copy(vec.elements, vec.elements + vec.length, elements);
}
};
正确实现:
cpp
Vector(const Vector& vec) : length(vec.length) { // 先初始化length
elements = new int[length];
copy(vec.elements, vec.elements + length, elements);
}
7.3 最佳实践:遵循 Rule of Three/Five
- Rule of Three(C++98):若类需要自定义析构函数、复制构造函数或拷贝赋值运算符中的任意一个,通常需要同时定义这三个函数。
- Rule of Five(C++11):新增移动构造函数和移动赋值运算符,若需要其中一个,通常需定义五个函数(析构 + 复制 + 移动)。
示例:完整资源管理类
cpp
#include <iostream>
#include <algorithm>
class ResourceManager {
public:
// 构造函数
ResourceManager(int size) : data(new int[size]), length(size) {
std::cout << "Constructor called with size: " << length << std::endl;
}
// 析构函数
~ResourceManager() {
std::cout << "Destructor called for size: " << length << std::endl;
delete[] data;
}
// 复制构造函数
ResourceManager(const ResourceManager& rhs) :
data(new int[rhs.length]), length(rhs.length) {
std::cout << "Copy constructor called for size: " << length << std::endl;
std::copy(rhs.data, rhs.data + length, data);
}
// 拷贝赋值运算符
ResourceManager& operator=(const ResourceManager& rhs) {
std::cout << "Copy assignment operator called for size: " << length << std::endl;
if (this != &rhs) {
delete[] data;
data = new int[rhs.length];
length = rhs.length;
std::copy(rhs.data, rhs.data + length, data);
}
return *this;
}
// 移动构造函数
ResourceManager(ResourceManager&& rhs) noexcept :
data(rhs.data), length(rhs.length) {
std::cout << "Move constructor called for size: " << length << std::endl;
rhs.data = nullptr;
rhs.length = 0;
}
// 移动赋值运算符
ResourceManager& operator=(ResourceManager&& rhs) noexcept {
std::cout << "Move assignment operator called for size: " << length << std::endl;
if (this != &rhs) {
delete[] data;
data = rhs.data;
length = rhs.length;
rhs.data = nullptr;
rhs.length = 0;
}
return *this;
}
private:
int* data;
int length;
};
int main() {
// 测试构造函数
ResourceManager rm1(5);
// 测试复制构造函数
ResourceManager rm2(rm1);
// 测试拷贝赋值运算符
ResourceManager rm3(3);
rm3 = rm1;
// 测试移动构造函数
ResourceManager rm4(std::move(ResourceManager(7)));
// 测试移动赋值运算符
ResourceManager rm5(2);
rm5 = std::move(ResourceManager(9));
return 0;
}

八、总结
复制构造函数是 C++ 中对象复制的核心机制,其设计直接影响程序的正确性和性能。关键点总结:
- 何时需要自定义:当类中包含资源管理成员(如指针、文件句柄)时,必须定义复制构造函数实现深拷贝。
- 避免浅拷贝陷阱:默认复制构造函数的浅拷贝会导致资源共享,引发悬挂指针和双重释放。
- 结合现代 C++ 特性 :利用移动语义(
std::move
)和const
引用提升性能与安全性。 - 遵循资源管理规则:严格遵守 Rule of Three/Five,确保析构函数、复制 / 移动函数的一致性。
九、参考资料
-
**《C++ Primer(第 5 版)》**这本书是 C++ 领域的经典之作,对 C++ 的基础语法和高级特性都有深入讲解。
-
**《Effective C++(第 3 版)》**书中包含了很多 C++ 编程的实用建议和最佳实践。
-
《C++ Templates: The Complete Guide(第 2 版)》 该书聚焦于 C++ 模板编程,而
using
声明在模板编程中有着重要应用,如定义模板类型别名等。 -
C++ 官方标准文档:C++ 标准文档是最权威的参考资料,可以查阅最新的 C++ 标准(如 C++11、C++14、C++17、C++20 等)文档。例如,ISO/IEC 14882:2020 是 C++20 标准的文档,可从相关渠道获取其详细内容。
-
:这是一个非常全面的 C++ 在线参考网站,提供了详细的 C++ 语言和标准库文档。
-
:该网站提供了系统的 C++ 教程,配有丰富的示例代码和清晰的解释,适合初学者学习和理解相关知识。
-
《C++标准库(第2版)》Nicolai M. Josuttis 著
-
Effective STL Scott Meyers 著
-
C++ Core Guidelines:C++ Core Guidelines
-
C++ Reference:https://en.cppreference.com/w/