Featured image of post C++构造函数与拷贝控制完全指南

C++构造函数与拷贝控制完全指南

概述

在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};

最佳实践和建议

  1. Rule of Three/Five

    • 如果你需要自定义析构函数、拷贝构造函数或拷贝赋值运算符中的任何一个,那么很可能需要定义所有三个(Rule of Three)
    • C++11后,还应考虑移动构造函数和移动赋值运算符(Rule of Five)
  2. 使用 = default 和 = delete

    1class Student {
    2public:
    3    Student() = default;           // 使用编译器生成的默认构造函数
    4    Student(const Student&) = delete; // 禁止拷贝
    5    Student(Student&&) = default;  // 使用编译器生成的移动构造函数
    6};
  3. 优先使用移动语义:对于管理资源的类,实现移动操作可以显著提高性能。

  4. 注意异常安全:移动操作通常应该标记为noexcept

  5. 深拷贝 vs 浅拷贝

    • 浅拷贝:只复制指针,不复制指向的数据
    • 深拷贝:复制指针和指向的数据(需要自己实现)

总结

  • 默认构造:创建空对象
  • 参数化构造:带参数初始化
  • 拷贝构造:用旧对象创建新对象
  • 移动构造:偷取临时对象资源创建新对象
  • 拷贝赋值:两个已存在对象之间的赋值
  • 移动赋值:偷取临时对象资源给已存在对象赋值
  • 析构函数:清理资源