拷贝、赋值与销毁(1)
拷贝、赋值与销毁(1)
Posted on 2019-01-11 15:38 Summer_8918 阅读(…) 评论(…) 编辑 收藏
学习类如何控制对象拷贝、赋值、移动或销毁时做什么。类通过一些特殊的成员函数控制这些操作,包括:拷贝构造函数、移动构造函数、拷贝赋值运算符、移动运算符以及析构函数。
当定义一个类时,我们显式或隐式地指定在此类型非对象拷贝、移动、赋值和销毁时做什么。一个类通过定义五种特殊的成员函数来控制这些操作,包括:拷贝构造函数、拷贝赋值函数、移动构造函数、移动赋值函数和析构函数。
拷贝和移动构造函数定义了当用同类型的另一个对象初始化本对象时做什么。拷贝和移动赋值运算符定义了将一个对象赋予同类型的另一个对象时做什么。
析构函数定义了当此类型对象销毁时做什么。
称这些操作为拷贝控制。
1、拷贝构造函数
如果一个构造函数的第一个参数是自身类类型的引用,且任何额外参数都有默认值,则此构造函数是拷贝构造函数。拷贝构造函数通常不应该是explicit的
class Foo{
public:
Foo(); //默认构造函数
Foo(const Foo&) //拷贝构造函数
}
合成拷贝构造函数
如果我们没有为一个类定义拷贝构造函数,编译器会为我们定义一个。
一般情况下,合成的拷贝构造函数会将其参数的成员逐个拷贝到正在创建的对象中。编译器会从给定对象中一次将每个非static成员拷贝到正在创建的对象中。
每个成员的类型决定了它如何拷贝:对类类型的成员,会使用期拷贝构造函数来拷贝;内置类型的成员则直接拷贝。
拷贝初始化通常用拷贝构造函数来完成。
拷贝构造函数使用的情况:
(1) 用=定义变量时
(2) 将一个对象作为实参传递给一个非引用类型的形参
(3) 从一个返回类型为非引用类型的函数返回一个对象
(4) 用花括号列表初始化一个数组中的元素或一个聚合类的成员
(5) 初始化标准库容器或调用其insert/push操作时,容器会对其元素进行拷贝初始化
拷贝构造函数用来初始化非引用类型参数,这一特性解释了为什么拷贝构造函数自己的参数必须是引用类型。
编译器可以绕过拷贝构造函数,直接创建对象,但拷贝/移动构造函数必须是存在且可访问的(例如不能是private的)
1、拷贝赋值运算符
重载赋值运算符
重载运算符本质上是函数,其名字由operator关键字后接表示要定义的运算符的符号组成。重载运算符的参数表示运算符的运算对象。
class Foo{
public:
Foo& operator=(const Foo&); //赋值运算符
//
}
赋值运算符通常应该返回一个指向其左侧运算对象的引用
合成拷贝赋值运算符
如果类未定义自己的拷贝赋值运算符,编译器会为它合成一个。
2、析构函数
构造函数初始化对象的非static数据成员,可能还做一些其他的工作;析构函数释放对象使用的资源,并销毁对象的非static数据成员
class Foo{
public:
~Foo(); //析构函数
//…
}
由于析构函数不接受参数,因此他不能被重载,对一个给定的类,唯一一个析构函数。
析构函数完成什么工作
析构函数有一个函数体和一个析构部分。在一个析构函数中,首先执行函数体,然后销毁成员,按成员初始化顺序的逆序销毁。通常,析构函数会释放对象在生存期分配的所有资源。
成员销毁时发生什么完全依赖于成员的类型。销毁类类型的成员需要执行成员自己的析构函数,内置类型没有析构函数,因此销毁内置类型成员什么也不需要做。
隐式销毁一个内置指针类型的成员不会delete它所指向的对象。
与普通指针不同,智能指针是类类型,具有析构函数,智能指针成员在析构阶段会被自动销毁
什么时候调用析构函数
(1) 变量离开其作用域
(2) 当一个对象被销毁时,其成员被销毁
(3) 容器(标准库容器或数组)被销毁时,其元素被销毁
(4) 对于动态分配的对象,当对指向它的指针应用delete运算符时被销毁
(5) 对于临时对象,当创建它的完整表达式结束时被销毁
当指向一个对象的引用或指针离开作用域时,析构函数不会执行