C++学习笔记-拷贝构造函数

拷贝构造函数是用于创建对象副本的特殊构造函数。理解浅拷贝和深拷贝的区别,以及如何正确实现拷贝构造函数,对于避免内存管理问题至关重要。

默认拷贝构造函数的问题

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
#include <iostream>
#include <cstring>

class String
{
private:
    char* m_Buffer;
    size_t m_Size;
public:
    String(const char* str)
    {
        m_Size = strlen(str);
        m_Buffer = new char[m_Size + 1];
        memcpy(m_Buffer, str, m_Size);
        m_Buffer[m_Size] = 0;
        std::cout << "Created string: " << str << std::endl;
    }

    // 如果不定义拷贝构造函数,编译器会提供一个默认的
    // 默认拷贝构造函数只是简单的浅拷贝
    /*
    String(const String& other)
    {
        m_Size = other.m_Size;
        m_Buffer = other.m_Buffer; // 浅拷贝:只复制指针
    }
    */

    ~String()
    {
        std::cout << "Destroying string" << std::endl;
        delete[] m_Buffer;
    }

    const char* GetBuffer() const { return m_Buffer; }
    
    char& operator[](unsigned int index)
    {
        return m_Buffer[index];
    }
};

int main()
{
    String str1 = "Jerry";
    String str2 = str1;  // 调用拷贝构造函数
    
    // 使用默认拷贝构造函数会导致问题:
    // 1. 两个对象共享同一块内存
    // 2. 析构时会重复删除同一块内存(崩溃)
    // 3. 修改一个对象会影响另一个对象
}

正确的深拷贝实现

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
class SafeString
{
private:
    char* m_Buffer;
    size_t m_Size;
public:
    SafeString(const char* str)
    {
        m_Size = strlen(str);
        m_Buffer = new char[m_Size + 1];
        memcpy(m_Buffer, str, m_Size);
        m_Buffer[m_Size] = 0;
        std::cout << "Created string: " << str << std::endl;
    }

    // 自定义拷贝构造函数 - 深拷贝
    SafeString(const SafeString& other)
    {
        std::cout << "Copy constructor called" << std::endl;
        m_Size = other.m_Size;
        m_Buffer = new char[m_Size + 1];  // 分配新内存
        memcpy(m_Buffer, other.m_Buffer, m_Size);  // 复制数据
        m_Buffer[m_Size] = 0;
    }

    ~SafeString()
    {
        std::cout << "Destroying string: " << m_Buffer << std::endl;
        delete[] m_Buffer;
    }

    const char* GetBuffer() const { return m_Buffer; }
    
    char& operator[](unsigned int index)
    {
        return m_Buffer[index];
    }
    
    // 重载输出运算符
    friend std::ostream& operator<<(std::ostream& ostream, const SafeString& string);
};

std::ostream& operator<<(std::ostream& ostream, const SafeString& string)
{
    ostream << string.m_Buffer;
    return ostream;
}

int main()
{
    SafeString str1 = "Jerry";
    SafeString str2 = str1;  // 深拷贝

    std::cout << "str1: " << str1 << std::endl;
    std::cout << "str2: " << str2 << std::endl;

    str2[1] = 'a';  // 修改str2不会影响str1
    std::cout << "After modifying str2:" << std::endl;
    std::cout << "str1: " << str1 << std::endl;
    std::cout << "str2: " << str2 << std::endl;
}

拷贝构造函数的调用时机

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
class Example
{
private:
    int m_Value;
public:
    Example(int value) : m_Value(value)
    {
        std::cout << "Constructor: " << m_Value << std::endl;
    }
    
    Example(const Example& other) : m_Value(other.m_Value)
    {
        std::cout << "Copy constructor: " << m_Value << std::endl;
    }
    
    ~Example()
    {
        std::cout << "Destructor: " << m_Value << std::endl;
    }
    
    int GetValue() const { return m_Value; }
};

// 按值传递参数
void FunctionByValue(Example obj)
{
    std::cout << "In function: " << obj.GetValue() << std::endl;
}

// 按值返回
Example FunctionReturnByValue()
{
    Example local(100);
    return local;  // 可能调用拷贝构造函数(取决于编译器优化)
}

int main()
{
    std::cout << "=== Creating objects ===" << std::endl;
    Example obj1(42);
    Example obj2 = obj1;        // 拷贝构造函数
    Example obj3(obj1);         // 拷贝构造函数
    
    std::cout << "\n=== Function call by value ===" << std::endl;
    FunctionByValue(obj1);      // 拷贝构造函数
    
    std::cout << "\n=== Function return by value ===" << std::endl;
    Example obj4 = FunctionReturnByValue();  // 可能的拷贝构造函数
    
    std::cout << "\n=== End of main ===" << std::endl;
}

拷贝赋值运算符

拷贝构造函数和拷贝赋值运算符是不同的:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
class CompleteString
{
private:
    char* m_Buffer;
    size_t m_Size;
    
public:
    CompleteString(const char* str)
    {
        m_Size = strlen(str);
        m_Buffer = new char[m_Size + 1];
        memcpy(m_Buffer, str, m_Size);
        m_Buffer[m_Size] = 0;
    }

    // 拷贝构造函数
    CompleteString(const CompleteString& other)
    {
        std::cout << "Copy constructor" << std::endl;
        m_Size = other.m_Size;
        m_Buffer = new char[m_Size + 1];
        memcpy(m_Buffer, other.m_Buffer, m_Size);
        m_Buffer[m_Size] = 0;
    }

    // 拷贝赋值运算符
    CompleteString& operator=(const CompleteString& other)
    {
        std::cout << "Copy assignment operator" << std::endl;
        
        // 自赋值检查
        if (this == &other)
            return *this;
            
        // 释放原有内存
        delete[] m_Buffer;
        
        // 分配新内存并拷贝
        m_Size = other.m_Size;
        m_Buffer = new char[m_Size + 1];
        memcpy(m_Buffer, other.m_Buffer, m_Size);
        m_Buffer[m_Size] = 0;
        
        return *this;
    }

    ~CompleteString()
    {
        delete[] m_Buffer;
    }

    const char* GetBuffer() const { return m_Buffer; }
};

int main()
{
    CompleteString str1("Hello");
    CompleteString str2 = str1;    // 拷贝构造函数
    CompleteString str3("World");
    str3 = str1;                   // 拷贝赋值运算符
}

禁用拷贝

有时我们不希望对象被拷贝:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
class NonCopyable
{
private:
    int* m_Data;
    
public:
    NonCopyable(int value)
    {
        m_Data = new int(value);
    }
    
    // C++11方式:使用delete禁用拷贝
    NonCopyable(const NonCopyable&) = delete;
    NonCopyable& operator=(const NonCopyable&) = delete;
    
    // C++98方式:声明为private但不实现
    // private:
    //     NonCopyable(const NonCopyable&);
    //     NonCopyable& operator=(const NonCopyable&);
    
    ~NonCopyable()
    {
        delete m_Data;
    }
    
    int GetValue() const { return *m_Data; }
};

int main()
{
    NonCopyable obj1(42);
    // NonCopyable obj2 = obj1;  // 编译错误:拷贝构造函数被删除
    // NonCopyable obj3(100);
    // obj3 = obj1;              // 编译错误:拷贝赋值运算符被删除
}

移动语义(C++11)

现代C++提供了移动构造函数作为拷贝的高效替代:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
class ModernString
{
private:
    char* m_Buffer;
    size_t m_Size;
    
public:
    ModernString(const char* str)
    {
        m_Size = strlen(str);
        m_Buffer = new char[m_Size + 1];
        memcpy(m_Buffer, str, m_Size);
        m_Buffer[m_Size] = 0;
        std::cout << "Constructor: " << str << std::endl;
    }

    // 拷贝构造函数
    ModernString(const ModernString& other)
    {
        std::cout << "Copy constructor" << std::endl;
        m_Size = other.m_Size;
        m_Buffer = new char[m_Size + 1];
        memcpy(m_Buffer, other.m_Buffer, m_Size);
        m_Buffer[m_Size] = 0;
    }

    // 移动构造函数
    ModernString(ModernString&& other) noexcept
    {
        std::cout << "Move constructor" << std::endl;
        m_Size = other.m_Size;
        m_Buffer = other.m_Buffer;
        
        // 清空源对象
        other.m_Size = 0;
        other.m_Buffer = nullptr;
    }

    ~ModernString()
    {
        delete[] m_Buffer;
    }

    const char* GetBuffer() const { return m_Buffer; }
};

ModernString CreateString()
{
    return ModernString("Temporary");
}

int main()
{
    ModernString str1("Hello");
    ModernString str2 = str1;              // 拷贝构造函数
    ModernString str3 = std::move(str1);   // 移动构造函数
    ModernString str4 = CreateString();    // 移动构造函数(RVO可能优化掉)
}

三法则和五法则

三法则(C++98)

如果需要自定义以下任何一个,通常需要自定义全部三个:

  1. 析构函数
  2. 拷贝构造函数
  3. 拷贝赋值运算符

五法则(C++11)

在三法则基础上增加: 4. 移动构造函数 5. 移动赋值运算符

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
class RuleOfFive
{
private:
    int* m_Data;
    size_t m_Size;
    
public:
    // 构造函数
    RuleOfFive(size_t size) : m_Size(size)
    {
        m_Data = new int[size];
    }
    
    // 1. 析构函数
    ~RuleOfFive()
    {
        delete[] m_Data;
    }
    
    // 2. 拷贝构造函数
    RuleOfFive(const RuleOfFive& other) : m_Size(other.m_Size)
    {
        m_Data = new int[m_Size];
        std::copy(other.m_Data, other.m_Data + m_Size, m_Data);
    }
    
    // 3. 拷贝赋值运算符
    RuleOfFive& operator=(const RuleOfFive& other)
    {
        if (this != &other)
        {
            delete[] m_Data;
            m_Size = other.m_Size;
            m_Data = new int[m_Size];
            std::copy(other.m_Data, other.m_Data + m_Size, m_Data);
        }
        return *this;
    }
    
    // 4. 移动构造函数
    RuleOfFive(RuleOfFive&& other) noexcept
        : m_Data(other.m_Data), m_Size(other.m_Size)
    {
        other.m_Data = nullptr;
        other.m_Size = 0;
    }
    
    // 5. 移动赋值运算符
    RuleOfFive& operator=(RuleOfFive&& other) noexcept
    {
        if (this != &other)
        {
            delete[] m_Data;
            m_Data = other.m_Data;
            m_Size = other.m_Size;
            other.m_Data = nullptr;
            other.m_Size = 0;
        }
        return *this;
    }
};

总结

  1. 拷贝构造函数:用于创建对象的副本,需要理解浅拷贝和深拷贝的区别
  2. 默认行为:编译器提供的默认拷贝构造函数只进行浅拷贝,对于管理动态内存的类可能导致问题
  3. 深拷贝:自定义拷贝构造函数实现深拷贝,为每个对象分配独立的内存
  4. 调用时机:对象初始化、函数参数传递、函数返回值等情况下会调用拷贝构造函数
  5. 最佳实践:遵循三法则或五法则,考虑使用智能指针避免手动内存管理
updatedupdated2025-09-202025-09-20