三五法则

  1. 如果一个类定义了解构函数,那么您必须同时定义或删除拷贝构造函数和拷贝赋值函数,否则出错。
  2. 如果一个类定义了拷贝构造函数,那么您必须同时定义或删除拷贝赋值函数,否则出错,删除可导致低效。
  3. 如果一个类定义了移动构造函数,那么您必须同时定义或删除移动赋值函数,否则出错,删除可导致低效。
  4. 如果一个类定义了拷贝构造函数或拷贝赋值函数,那么您必须最好同时定义移动构造函数或移动赋值函数,否则低效。

针对C++89,在定义一个类时,可以显示(手动定义)/隐式(编译器默认)定义该类对象在拷贝、赋值、解构时如何做。

一个类通过定义三种特殊成员成员函数来控制这些操作:拷贝构造函数、拷贝赋值函数、析构函数。

所以三法则:

  1. 如果一个类定义了解构函数,那么您必须同时定义或删除拷贝构造函数和拷贝赋值函数,否则出错
  2. 如果一个类定义了拷贝构造函数,那么您必须同时定义或删除拷贝赋值函数,否则出错,删除可导致低效。

因为C++默认提供的拷贝赋值函数和拷贝构造函数是对成员进行浅拷贝,非常容易出现内存管理问题。

1.禁用拷贝函数:

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
// Myvector.h
class Myvector {
public:
size_t m_size;
int *m_data;

Myvector(size_t n); // 构造函数
~Myvector(); // 析构函数

Myvector(Myvector const &) = delete; // 禁用拷贝构造函数

size_t size();
void resize(size_t size);
int& operator[](size_t index);
};

// main.cpp
int main () {
Myvector v1(2);

Myvector v2 = v1; // 拷贝构造函数
// Myvector v2(v1);

return 0;
}

运行结果:

1
2
3
4
5
main.cpp:12:14: error: call to deleted constructor of 'Myvector'
Myvector v2 = v1; // 拷贝构造函数
^ ~~
myvector.h:13:5: note: 'Myvector' has been explicitly marked deleted here
Myvector(Myvector const &) = delete; // 禁用拷贝构造函数

2.定义拷贝构造函数,拷贝赋值函数:

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
// main.cpp
int main () {
Myvector v1(2);
v1[0] = 4; v1[1] = 3;

Myvector v2 = v1; // 拷贝构造
Myvector v3(v1); // 拷贝构造
v3 = v2; // 拷贝赋值

for (int i = 0; i < v1.m_size; i ++)
cout << v1[i] << endl;

for (int i = 0; i < v2.m_size; i ++)
cout << v2[i] << endl;

for (int i = 0; i < v3.m_size; i ++)
cout << v3[i] << endl;

return 0;
}

// Myvector.cpp
Myvector::Myvector(Myvector const &other)
{ //拷贝构造函数
std::cout << "拷贝构造" << std::endl;
m_size = other.m_size;
m_data = (int *)malloc(sizeof(int) * m_size);
memcpy(m_data, other.m_data, sizeof(int) * m_size);
}

Myvector& Myvector::operator=(Myvector const &other)
{// 拷贝赋值函数
std::cout << "拷贝赋值" << std::endl;
// 性能较弱,先解构再在内存三重新new一个
// this->~Myvector();
// new (this) Myvector(other); // placement new
// return *this;

// 利用当前现有的m_data,realloc避免重新分配
m_size = other.m_size;
m_data = (int *)realloc(m_data, sizeof(int) * m_size);
memcpy(m_data, other.m_data, m_size * sizeof(int));
return *this;
}

运行结果:

1
2
3
拷贝构造
拷贝构造
拷贝赋值

在较新的 C++11 标准中,为了支持移动语义,又增加了移动构造函数和移动赋值运算符,这样共有五个特殊的成员函数,所以又称为“C++五法则”。

  1. 如果一个类定义了移动构造函数,那么您必须同时定义或删除移动赋值函数,否则出错,删除可导致低效。
  2. 如果一个类定义了拷贝构造函数或拷贝赋值函数,那么您必须最好同时定义移动构造函数或移动赋值函数,否则低效。

定义移动构造函数和移动赋值函数:

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
// main.cpp
int main () {
Myvector v1(2);
v1[0] = 4; v1[1] = 3;

Myvector v2(2);
v2[0] = 2; v2[1] = 1;

Myvector v3(2);
v3[0] = 0; v3[1] = -1;

v2 = v1; // 拷贝赋值
cout << "V2 Length: " << v2.size() << " V1 Length: " << v1.size() << endl;

v3 = move(v1); // 移动赋值
cout << "V3 Length: " << v3.size() << " V1 Length: " << v1.size() << endl;

return 0;
}

// Myvector.hpp
class Myvector {
public:
size_t m_size;
int *m_data;

Myvector(size_t n); // 构造函数
~Myvector(); // 析构函数

Myvector(Myvector const &other); // 定义拷贝构造函数
Myvector& operator=(Myvector const &other); // 定义拷贝赋值函数

Myvector(Myvector &&other); // 移动构造
Myvector& operator=(Myvector &&other); // 移动赋值

size_t size();
void resize(size_t size);
int& operator[](size_t index);
};

// Myvector.cpp
Myvector::Myvector(Myvector &&other)
{// 移动构造
std::cout << "移动构造" << std::endl;
m_size = other.m_size;
m_data = other.m_data;
other.m_size = 0;
other.m_data = nullptr;
}

Myvector& Myvector::operator=(Myvector &&other)
{// 移动赋值
this->~Myvector();
new (this) Myvector(std::move(other));
return *this;
}

运行结果:

1
2
3
4
拷贝赋值
V2 Length: 2 V1 Length: 2
移动构造
V3 Length: 2 V1 Length: 0