概述
在C++中,对象的创建、复制、移动和销毁是由一组特殊的成员函数管理的,这些函数统称为"拷贝控制成员"。理解这些函数的工作原理对于编写安全、高效的C++代码至关重要。本文将详细讲解各种构造函数和拷贝控制成员的使用方法和区别。
构造函数家族
1. 默认构造函数 (Default Constructor)
默认构造函数是在没有提供任何参数时调用的构造函数。
1class Student {
2public:
3 // 默认构造函数
4 Student() : name("未知"), age(0) {
5 cout << "默认构造函数被调用" << endl;
6 }
7
8 string name;
9 int age;
10};
11
12// 使用示例
13Student s1; // 调用默认构造函数
14Student s2 = Student(); // 显式调用
2. 参数化构造函数 (Parameterized Constructor)
接受参数的构造函数,用于初始化对象成员。
1class Student {
2public:
3 // 参数化构造函数
4 Student(string n, int a) : name(n), age(a) {
5 cout << "参数化构造函数被调用" << endl;
6 }
7
8 string name;
9 int age;
10};
11
12// 使用示例
13Student s1("张三", 20);
14Student s2 = Student("李四", 21);
3. 拷贝构造函数 (Copy Constructor)
用于用一个已存在的对象初始化一个新对象。
1class Student {
2public:
3 Student(string n, int a) : name(n), age(a) {}
4
5 // 拷贝构造函数
6 Student(const Student& other)
7 : name(other.name + "(副本)"), age(other.age) {
8 cout << "拷贝构造函数被调用" << endl;
9 }
10
11 string name;
12 int age;
13};
14
15// 使用示例
16Student s1("张三", 20);
17Student s2 = s1; // 调用拷贝构造函数
18Student s3(s1); // 另一种调用方式
4. 移动构造函数 (Move Constructor)
C++11引入,用于"偷取"临时对象的资源,提高性能。
1class Student {
2public:
3 // 移动构造函数
4 Student(Student&& other) noexcept
5 : name(std::move(other.name)), age(other.age) {
6 other.age = 0; // 源对象置为空状态
7 cout << "移动构造函数被调用" << endl;
8 }
9
10 string name;
11 int age;
12};
13
14// 使用示例
15Student createStudent() {
16 return Student("临时学生", 19); // 可能触发移动构造
17}
18Student s = createStudent(); // 移动构造
拷贝控制成员
1. 拷贝赋值运算符 (Copy Assignment Operator)
用于两个已存在对象之间的赋值操作。
1class Student {
2public:
3 // 拷贝赋值运算符
4 Student& operator=(const Student& other) {
5 if (this != &other) { // 防止自赋值
6 name = other.name;
7 age = other.age;
8 cout << "拷贝赋值运算符被调用" << endl;
9 }
10 return *this;
11 }
12
13 string name;
14 int age;
15};
16
17// 使用示例
18Student s1("张三", 20);
19Student s2("李四", 21);
20s2 = s1; // 调用拷贝赋值运算符
2. 移动赋值运算符 (Move Assignment Operator)
用于给已存在对象赋值时"偷取"另一个对象的资源。
1class Student {
2public:
3 // 移动赋值运算符
4 Student& operator=(Student&& other) noexcept {
5 if (this != &other) {
6 name = std::move(other.name);
7 age = other.age;
8 other.age = 0; // 源对象置空
9 cout << "移动赋值运算符被调用" << endl;
10 }
11 return *this;
12 }
13
14 string name;
15 int age;
16};
17
18// 使用示例
19Student s1("张三", 20);
20Student s2("李四", 21);
21s2 = std::move(s1); // 移动赋值
3. 析构函数 (Destructor)
在对象销毁时自动调用,用于清理资源。
1class Student {
2public:
3 Student() {
4 data = new int[100]; // 动态分配内存
5 }
6
7 // 析构函数
8 ~Student() {
9 delete[] data; // 释放内存
10 cout << "析构函数被调用" << endl;
11 }
12
13 int* data;
14};
15
16// 使用示例
17{
18 Student s; // 构造函数被调用
19} // 离开作用域,析构函数被调用
关键区别与使用场景
移动构造 vs 移动赋值
特性 | 移动构造函数 | 移动赋值运算符 |
---|---|---|
用途 | 创建新对象 | 给已有对象赋值 |
函数签名 | Student(Student&&) | operator=(Student&&) |
返回值 | 无 | Student& |
资源清理 | 不需要 | 需要先清理自身资源 |
自赋值检查 | 不需要 | 需要 |
调用时机 | 创建新对象时 | 已有对象赋值时 |
移动构造函数示例:
1// 创建新对象并"偷取"资源
2Student s2 = std::move(s1); // 移动构造
移动赋值运算符示例:
1// 给已有对象赋值并"偷取"资源
2Student s2("李四", 21);
3s2 = std::move(s1); // 移动赋值
拷贝构造 vs 移动构造
特性 | 拷贝构造函数 | 移动构造函数 |
---|---|---|
参数类型 | const Student& | Student&& |
资源处理 | 深拷贝资源 | 转移资源所有权 |
源对象状态 | 保持不变 | 被置为空状态 |
性能 | 较低(需要复制) | 较高(只需转移) |
完整示例类
1class Student {
2private:
3 string name;
4 int age;
5 int* scores; // 动态数组
6 int size;
7
8public:
9 // 默认构造函数
10 Student() : name("未知"), age(0), scores(nullptr), size(0) {
11 cout << "默认构造函数" << endl;
12 }
13
14 // 参数化构造函数
15 Student(string n, int a, int* s, int sz)
16 : name(n), age(a), size(sz) {
17 scores = new int[size];
18 for (int i = 0; i < size; i++) scores[i] = s[i];
19 cout << "参数化构造函数" << endl;
20 }
21
22 // 拷贝构造函数
23 Student(const Student& other)
24 : name(other.name), age(other.age), size(other.size) {
25 scores = new int[size];
26 for (int i = 0; i < size; i++) scores[i] = other.scores[i];
27 cout << "拷贝构造函数" << endl;
28 }
29
30 // 移动构造函数
31 Student(Student&& other) noexcept
32 : name(std::move(other.name)), age(other.age),
33 scores(other.scores), size(other.size) {
34 other.scores = nullptr;
35 other.size = 0;
36 cout << "移动构造函数" << endl;
37 }
38
39 // 拷贝赋值运算符
40 Student& operator=(const Student& other) {
41 if (this != &other) {
42 delete[] scores;
43
44 name = other.name;
45 age = other.age;
46 size = other.size;
47 scores = new int[size];
48 for (int i = 0; i < size; i++) scores[i] = other.scores[i];
49
50 cout << "拷贝赋值运算符" << endl;
51 }
52 return *this;
53 }
54
55 // 移动赋值运算符
56 Student& operator=(Student&& other) noexcept {
57 if (this != &other) {
58 delete[] scores;
59
60 name = std::move(other.name);
61 age = other.age;
62 scores = other.scores;
63 size = other.size;
64
65 other.scores = nullptr;
66 other.size = 0;
67
68 cout << "移动赋值运算符" << endl;
69 }
70 return *this;
71 }
72
73 // 析构函数
74 ~Student() {
75 delete[] scores;
76 cout << "析构函数" << endl;
77 }
78};
最佳实践和建议
Rule of Three/Five:
- 如果你需要自定义析构函数、拷贝构造函数或拷贝赋值运算符中的任何一个,那么很可能需要定义所有三个(Rule of Three)
- C++11后,还应考虑移动构造函数和移动赋值运算符(Rule of Five)
使用 = default 和 = delete:
1class Student { 2public: 3 Student() = default; // 使用编译器生成的默认构造函数 4 Student(const Student&) = delete; // 禁止拷贝 5 Student(Student&&) = default; // 使用编译器生成的移动构造函数 6};
优先使用移动语义:对于管理资源的类,实现移动操作可以显著提高性能。
注意异常安全:移动操作通常应该标记为
noexcept
。深拷贝 vs 浅拷贝:
- 浅拷贝:只复制指针,不复制指向的数据
- 深拷贝:复制指针和指向的数据(需要自己实现)
总结
- 默认构造:创建空对象
- 参数化构造:带参数初始化
- 拷贝构造:用旧对象创建新对象
- 移动构造:偷取临时对象资源创建新对象
- 拷贝赋值:两个已存在对象之间的赋值
- 移动赋值:偷取临时对象资源给已存在对象赋值
- 析构函数:清理资源