13.1 拷贝、赋值与销毁
拷贝构造函数
拷贝构造的第一个参数是自身类类型的引用,且任何额外参数都有默认值,成为拷贝构造函数。
拷贝构造函数通常不应该是 explicit
,可以接受非 const
引用。
class Foo{
public:
Foo();
Foo(const Foo &);
...
};
合成拷贝构造函数
编译器会自动合成拷贝构造函数。一般情况中,合成的拷贝构造函数会将其参数的成员逐个拷贝到正在创建的对象中。编译器从给定对象中依次将每个非 static 成员拷贝到正在创建的对象中。
成员的类型决定了拷贝的方式:
- 类:使用拷贝构造函数
- 内置类型:直接拷贝
- 数组:逐元素拷贝,如果元素是类,逐元素使用拷贝构造函数
拷贝初始化
拷贝初始化是依靠拷贝构造函数或移动构造函数来完成的。
拷贝初始化发生情况:
- 使用“=”定义变量
- 将一个对象作为实参传递给一个非引用类型的形参
- 从一个返回类型为非引用类型的函数返回一个对象
- 用花括号列表初始化一个数组中的元素或者一个聚合类中的成员
- 初始化标准容器库或调用insert或push时
拷贝初始化的限制
如果拷贝初始化需要通过explicit构造函数进行初始化,要显式使用。
编译器绕过拷贝构造函数
string null_book = "1246543213615"; //拷贝初始化
string null_book("1246543213615"); // 编译器略过拷贝构造函数
拷贝赋值运算符
重载赋值运算符
重载运算符的参数表示运算符的运算对象,如果是成员函数,左侧运算对象就绑定到隐式的this参数。
class Foo{
public:
Foo & operator=(const Foo&);
...
};
析构函数
析构函数释放对象在生存期分配使用的资源,销毁对象的非static数据成员。
内置类型没有析构函数。
隐式销毁一个内置指针类型的成员不会delete它所指向的对象。与普通指针不同,智能指针是类类型,具有析构函数。
调动析构函数情形:
- 变量在离开其作用域
- 对象被销毁,成员也销毁
- 容器(标准库、数组)被销毁,元素也销毁
- 动态分配的对象,指向对象的指针被delete时,对象被销毁
- 临时对象在结束使用时销毁
当指向一个对象的引用或指针离开作用域时,析构函数不会执行。
三/五法则
如果一个类需要自定义析构函数,则它也需要自定义拷贝构造函数和拷贝赋值运算符。
如果一个类需要拷贝构造函数,则它也需要一个拷贝赋值运算符,反之亦然。
使用=default
通过将拷贝控制成员函数/默认构造函数定义为=default显式要求编译器生成合成的版本。
class Sales_data{
public:
Sales_data() = default;
Sales_data(const Sales_data&) = default;
Sales_data& operator=(const Sales_data &);
~Sales_data() = default;
...
};
// 不是内联函数
Sales_data& Sales_data::operator=(const Sales_data&) = default;
阻止拷贝
例如,iostream 类阻止拷贝,避免多个对象写入或读取相同的IO缓冲。