13.1 Copy, Assign, and Destroy(拷贝,赋值和销毁)
13.1.1 拷贝构造
复制和移动构造定义一个对象在被另一个相同类型对象初始化时发生什么。
复制赋值操作符和移动赋值操作符定义一个类的一个对象赋值给同一一个类的另一个对象发生了什么。
在类当中,类类型的成员,通过拷贝构造复制,而内置类型(char, int...)的成员直接被复制
//direct initialization string str("hello"); string str1(10, 'a'); string str2(str1); //copy initialization string str3 = str; string str4 = "hello"; string str5 = str6(10, 'q');
拷贝初始化一般通过拷贝构造函数初始化,但如果一个类当中有转移构造函数,有时候会用转移构造函数替代拷贝构造函数。
拷贝初始化适用的情况:
- define variables using an =
- 将一个对象作为实参传入非引用形参当中
- 从一个拥有非引用返回类型的函数当中返回一个对象
- 括号初始化数组中的元素或聚合类的成员
- 库容器初始化它们的元素,当我们初始化容器,或者当我们调用insert或者push成员。而使用emplace是直接初始化。
在拷贝初始化期间,编译器是允许(但是没有义务)跳过拷贝/转移构造函数并且直接创造对象。
13.1.2 拷贝赋值操作符
当一个操作符是成员函数时,左边的运算对象被绑定到隐式的this形参。
在一个二元操作符的右边的运算对象,项赋值,是显示传递形参的。
赋值操作符通常应该返回一个引用到它的左边操作符
13.1.3 析构函数
没有参数不能被重载
成员被摧毁的顺序和它们初始化的顺序相反
析构函数被调用,每当它的类型被销毁时:
- 当变量超出范围时被销毁。
- 当成员变量被销毁,是它的部分的对象也会被销毁。
- 在容器中的元素(不管是库容器还是数组),当容器被销毁时,元素会被销毁。
- 动态分配对象被销毁,当delete用于指向对象的指针时。
- 在临时变量被创造的表达式末尾,临时变量被销毁。
13.1.4 三五法则
Classes that need destructors need copy and assgnment.//如果类需要析构函数,那么可以确定这个类也需要拷贝构造和拷贝赋值构造操作符。
Classes that need copy need assignment, and vice versa.//如果一个类需要一个拷贝构造,可以基本肯定它需要一个拷贝赋值操作符。反之亦然,
但如果一个函数需要拷贝构造或者拷贝赋值构造操作符,并不意味着它一定需要析构函数。
#include <iostream> #include <string> #include <vector> #include <algorithm> using namespace std; class HasPtr { public: HasPtr(const string &s = string()):ps(new string(s)), i(66){} //HasPtr():p(new string(*ps)), i(4){} HasPtr(HasPtr &a) : ps(new string(*a.ps)), i(a.i) { cout << "diao" << endl; } //HasPtr & operator=(const HasPtr &); HasPtr & operator=(HasPtr); HasPtr & operator=(const string &); string & operator*(); void Display(); friend void swap(HasPtr &, HasPtr &); ~HasPtr() { delete ps; } private: string *ps; //string *p; int i; }; void HasPtr::Display() { cout << i << " " << *ps << endl; } /* HasPtr & HasPtr::operator=(const HasPtr &rch) { this->i = rch.i; *this->ps = *rch.ps; return *this; }*/ string & HasPtr::operator*() { return *ps; } HasPtr & HasPtr::operator=(const string &s) { *ps = s; return *this; } /* HasPtr & HasPtr::operator=(const HasPtr &a) { this->i = a.i; *this->ps = *(a.ps); return *this; }*/ HasPtr & HasPtr::operator=(HasPtr a) { swap(*this, a); return *this; } inline void swap(HasPtr &rh1, HasPtr &rh2) { cout << "diaoyongzi" << endl; using std::swap; swap(rh1.ps, rh2.ps); swap(rh1.i, rh2.i); } int main() {/* string s = string(); cout << s << endl; HasPtr A("gou"), B("hello"), C(B); swap(A, B); A = B; A.Display(); */ vector<HasPtr> vec; HasPtr A("hello"), B("world"), C("biubiu"), D("guojia"); vec.push_back(A); vec.push_back(B); vec.push_back(C); vec.push_back(D); //sort(vec.begin(), vec.end()); //vec.front().Display(); system("PAUSE"); return 0; }
HasPtr f(HasPtr hp) { HasPtr ret = hp; return ret; }
当f返回值时,hp和ret类对象中的对象都会被销毁。析构函数将会销毁ret和hp元素中的指针成员。但这个对象包含相同的指针值,析构函数将会销毁两次那个指针,这样做是错误的。
HasPtr p("hello world"); f(p);//当f结束时,p.ps指向的内存被释放 HasPtr q(p);//p和q都指向无效内存
13.1.5 使用=default
当我们在类内部成员声明的末尾写出=default,那么这个合成函数被隐式的作为内联函数,如果我们不想要函数内联,可以在类的外部写出=default.
只有当成员函数有合成函数的版本时才能使用=default
13.1.6 阻止拷贝
在新标准下,我们能够通过定义拷贝构造和拷贝赋值构造为删除函数来阻止拷贝。
=delete通知编译器(和代码读者),我们不希望定义这些成员
- 与=default不同,=delete必须出现在函数第一次声明的时候。
- 我们可以对任何函数指定=delete
需要注意的是,我们不能删除析构函数。如果删除析构函数,则无法销毁此类型的对象。对于删除析构函数的类型,编译器将不允许定义该类型的变量或创建该类的临时对象。此外,如果一个类的成员类型的析构函数被删除,我们就不能为该类定义变量或临时对象,因为如果一个成员的析构函数被删除,它就不能被销毁。如果一个成员不能被销毁,那么对象作为一个整体就不能被销毁。
struct NoDtor{ NoDtor() = default;//使用合成默认构造函数 ~NoDtor() = delete;//我们不能销毁NoDtor的对象 }; NoDtor nd;//错误:NoDtor的析构函数是被删除的 NoDtor *p = new NoDtor();//正确,但是我们不能delete p delete p;//错误:NoDtor的析构函数是被删除的
合成拷贝控制成员定义为delete的情况:
- 如果类的某个成员的析构函数是删除的或不可访问的(例如,是private的),则类的合成析构函数被定义为删除的。
- 如果类的某个成员的拷贝构造函数是删除的或不可访问的,则类的合成拷贝构造函数被定义为删除的,如果类的某个成员的析构函数是删除的或不可访问的,则类合成的拷贝构造函数也被定义为删除的。
- 如果类的某个成员的拷贝赋值运算符是删除的或不可访问的,或是类有一个const的或引用成员,则类的司成拷贝赋值运算符被定义为删除的。
- 如果类的某个成员的析构函数是删除的或不可访问的,或是类有一个引用成员,它没有类内初始化器,或是类有一个const成员,它没有类内初始化器且其类型未显式定义默认构造函数,则该类的默认构造函数被定义为删除的。
如果一个类有数据成员不能默认构造,拷贝,赋值或者销毁,那么对应的成员将是一个删除函数
编译器将不会给那些使用不能被默认构造的引用对象成员或者const成员的类生成合成函数
希望阻止拷贝的类应该使用=delete 来定义自己的拷贝构造函数和复制赋值操作符,而不是声明它们为private。

浙公网安备 33010602011771号