拷贝构造函数是用于创建对象副本的特殊构造函数。理解浅拷贝和深拷贝的区别,以及如何正确实现拷贝构造函数,对于避免内存管理问题至关重要。
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++提供了移动构造函数作为拷贝的高效替代:
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可能优化掉)
}
|
如果需要自定义以下任何一个,通常需要自定义全部三个:
- 析构函数
- 拷贝构造函数
- 拷贝赋值运算符
在三法则基础上增加:
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;
}
};
|
- 拷贝构造函数:用于创建对象的副本,需要理解浅拷贝和深拷贝的区别
- 默认行为:编译器提供的默认拷贝构造函数只进行浅拷贝,对于管理动态内存的类可能导致问题
- 深拷贝:自定义拷贝构造函数实现深拷贝,为每个对象分配独立的内存
- 调用时机:对象初始化、函数参数传递、函数返回值等情况下会调用拷贝构造函数
- 最佳实践:遵循三法则或五法则,考虑使用智能指针避免手动内存管理