通过定义5种成员函数控制一个类的copy(copy control).

  1. copy constructor
  2. copy-assignment operator
  3. move constructor
  4. move-assignment operator
  5. destructor

Copy, Assignment and Destructor

  1. copy constructor

    第一个参数是自身类型;所有额外参数都有默认值。

1
2
3
4
5
6
class Foo
{
public:
Foo();
Foo(const Foo&); // copy constructor
}

合成拷贝构造函数

complier会至少给class合成一个copy constructor,无论是定义还是编译器优化后的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Sales_data
{
public:
Sales_data(const Sales_data&);
private:
std::string bookNo;
int units_sold = 0;
double revenue = 0.0;
}

Sales_data::Sales_data(const Sales_data &orig):
bookNo(orig.bookNO), units_sold(orig.units_sold), revenus(orig.revenue) {}

// 直接初始化,实际是根据传入参数类型调用重载的构造函数
std::string dots(10, ".");
std::string s(dots);
// copy初始化,调用copy构造函数
std::string s2 = dots;

C++17后会尽量优化copy初始化,copy初始化性能较差,考虑隐性类型转换时使用,不支持explicit构造函数。

  1. 拷贝赋值运算符
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class DefaultCopy {
int x;
std::string s;
public:
// 编译器隐式生成:
// DefaultCopy& operator=(const DefaultCopy& other) {
// x = other.x;
// s = other.s; // 调用std::string::operator=
// return *this;
// }
};

DefaultCopy a, b;
a = b; // 使用编译器生成的拷贝赋值
  1. 析构函数

    对象销毁时调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class MyClass {
private:
int* data;
public:
// 构造函数
MyClass(int size) {
data = new int[size];
std::cout << "构造函数调用\n";
}

// 析构函数
~MyClass() {
delete[] data;
std::cout << "析构函数调用\n";
}
};
  1. 三五法则

    拷贝构造函数、拷贝赋值运算符、析构函数
    移动构造函数、移动赋值运算符

  2. = default

    显式要求编译器生成默认构造函数

1
2
3
4
5
6
7
8
9
10
class MyClass {
public:
// 显式要求编译器生成默认版本
MyClass() = default; // 默认构造函数
MyClass(const MyClass&) = default; // 拷贝构造函数
MyClass(MyClass&&) = default; // 移动构造函数
MyClass& operator=(const MyClass&) = default; // 拷贝赋值运算符
MyClass& operator=(MyClass&&) = default; // 移动赋值运算符
~MyClass() = default; // 析构函数
};

目的: 当提供了自定义的拷贝构造函数时,编译器不会自动生成默认的构造函数,如过需要的话,需要显式地指出。

  1. = delete

    拷贝函数定义为= delete,显式声明不允许以这种方式进行拷贝
    析构函数不可以定义为= delete,必须有销毁对象的方式

合成的拷贝控制成员函数也可能是删除的

  1. 类的某个成员的析构函数是删除或不可用的 -> 析构函数是删除的
  2. 类的某个成员的拷贝构造函数时删除或不可用的 -> 拷贝构造函数删除
  3. ————–拷贝赋值运算符————– -> 拷贝赋值运算符
  4. 析构函数删除或不可用,const或引用类型类成员没有类内初始化器 -> 默认构造函数是删除的

拷贝控制和资源管理

决定class是像一个值还是指针

像值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class HasPtr
{
public:
HasPtr(const std::string &s = std::string()): ps(new std::string(s)), i(0) {}
HasPtr(const HasPtr &p): ps(new std::string(*p.ps)), i(p.i) {}
// new会在堆上创建一块新的内存,然后这块动态内存会使用std::string的拷贝构造函数进行初始化,初始值为*p.ps
HasPtr& operator=(const HasPtr &);
~HasPtr() { delete ps; }
private:
std::string *ps;
int i;
};

HasPtr& HasPtr::operator=(const HasPtr &rhs)
{
auto newp = new string(*rhs.ps);
delete ps; // 如过rhs.ps与this.ps指向同一个对象,直接进行delete会导致内存先释放
ps = newp;
i = rhs.i;
return *this;
}

赋值运算符:1. 必须能正常工作;2. 组合了析构函数和拷贝构造函数的工作,避免内存泄漏

像指针

使用shared_ptr管理类资源,或者使用引用计数

引用计数工作方式

  1. 记录有多少对象与正在创建的对戏那个共享状态
  2. 拷贝构造函数不分配新的共享计数器,递增共享的计数器
  3. 析构函数递减计数器
  4. 拷贝赋值运算符递增右侧对象计数器,递减左侧对象计数器
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
class HasPtr
{
public:
HasPtr(const std::string &s = std::string()):
ps(new std::string(s)), i(0), use(new std::size_t(1)) {}
HasPtr(const HasPtr &p): ps(p.ps), i(p.i), use(p.use) { ++*use; }
HasPtr& operator=(const HasPtr&);
~HasPtr();
private:
std::string *ps;
int i;
std::size_t *use;
}
// 析构函数不能无条件直接将ps指向的std::string销毁,会导致其他共享指针的对象访问已经释放的内存
HasPtr::~HasPtr()
{
if(--*use == 0)
{
delete ps;
delete use;
}
}

// 拷贝赋值运算符:递增右侧计数器,递减左侧计数器
HasPtr& HasPtr::operator=(const HasPtr &rhs)
{
++*rhs.use;
if(--*use == 0) // 析构
{
delete ps;
delete use;
}
ps = rhs.ps;
i = rhs.i;
use = rhs.use;
return *this;
}

交换操作

内置数据使用std::swap(), 类使用内置swap()成员函数