第十三章 拷贝控制
声明: 本文为《C++ Primer 中文版(第五版)》学习笔记。 原书更为详细,本文仅作学习交流使用,未经授权禁止转载。
在公众号【Jacen的技术笔记】,回复 C++,即可获得 两万字C++ Primer 要点整理PDF。
P440-P486
五种拷贝控制操作:
拷贝构造函数、拷贝赋值运算符、移动构造函数、移动赋值运算符、析构函数。
拷贝构造函数、移动构造函数定义了当用同类型的另一个对象初始化本对象时做什么。
拷贝赋值运算符、移动赋值运算符定义了将一个对象赋予同类型的另一个对象时做什么。
析构函数定义了当此类型对象销毁时做什么。
13.1 拷贝、赋值与销毁
(1)拷贝构造函数
拷贝构造函数的第一个参数必须是一个引用类型。
class Foo {
public :
Foo(); // 默认构造函数
Foo(const Foo&); // 拷贝构造函数
}
合成拷贝构造函数:
若未定义拷贝构造函数,编译器会定义一个。
拷贝初始化:
拷贝初始化,要求编译器将右运算对象拷贝到正在创建的对象中。拷贝初始化通常使用拷贝构造函数来完成。
(2)拷贝赋值运算符
重载赋值运算符:oprator=
合成拷贝赋值运算符:若一个类未定义自己的拷贝赋值运算符,编译器会为它生成一个合成拷贝赋值运算符。
(3)析构函数
析构函数:用于释放对象使用的资源,销毁对象的非static数据成员。
class Foo {
public:
~Foo(); // 析构函数,一个类只会有唯一一个析构函数。
}
在一个析构函数中,不存在类似构造函数中初始化列表的东西来控制成员如何销毁,析构部分是隐式的。销毁类类型的成员需要执行成员自己的析构函数。
合成析构函数:当一个类未定义自己的析构函数时,编译器会为它定义一个合成析构函数。
析构函数体本身并不直接销毁成员。
(4)三五法则
P447
需要析构函数的类也需要拷贝和赋值操作
需要拷贝操作的类也需要赋值操作,反之亦然
(5)使用default=
将拷贝控制成员定义为=dafault来显式地要求编译器生活才能合成的版本。
class Sales_data {
public:
Sales_data(const Sales_data&) = default;
}
(6)阻止拷贝
在函数参数列表后面加上=delete。
=delete必须出现在函数第一次声明的时候。
析构函数不能是删除的成员
合成的拷贝控制成员可能是删除的:
如果一个类有数据成员不能默认构造、拷贝、复制或销毁,则对应的成员函数将被定义为删除的。
13.2 拷贝控制和资源管理
(1)行为像值的类
为了提供类值的行为,对于类管理的对象,每个对象都应该拥有一份自己的拷贝。
类值拷贝赋值运算符:通常组合了析构函数和构造函数的操作。
HasPtr& HasPtr::operator=(const HasPtr &rhs)
{
auto newp = new string(*rhs.ps);
delete ps;
ps = newp;
i = rhs.i;
return *this;
}
(2)行为像指针的类
如果需要可直接管理资源,可以使用引用计数。
13.3 交换操作
swap
13.4 拷贝控制示例
P460
13.5 动态内存管理类
P464
13.6 对象移动
与任何赋值运算符一样,移动赋值运算符必须销毁左侧运算对象的旧状态。
(1)右值引用
可通过move函数开获得绑定到左值上的右值引用。
int && rr3 = std::move(rr1);
(2)移动构造函数和移动赋值运算符
移动构造函数的第一个参数是该类类型的一个右值引用。
移动赋值运算符:
StrVec &StrVec::operator=(StrVec &&rhs) noexcept
{
}
合成的移动操作:
若一个类定义了自己的拷贝构造函数、拷贝赋值运算符或者析构函数,编译器就不会为它合成移动构造函数和移动赋值运算符。
如果一个类没有移动操作,类会使用对应的拷贝操作来代替移动操作。
移动迭代器:
移动迭代器的解引用运算符生成一个右值引用。
(3)右值引用和成员函数
::: tip
区分移动和拷贝的重载函数通常有一个版本接受一个const T&,而另一个版本接受一个T&&。
:::
右值和左值引用成员函数:
指出this的左值/右值属性的方式与定义const成员函数相同,在参数列表后放置一个引用限定符。P483
::: tip
如果一个成员函数有引用限定符,则具有相同参数列表的所有版本都必须有引用限定符。P485
:::
术语
引用限定符:被&限定的函数只能用于坐值;被&&限定的函数只能用于右值。