C++ Primer 学习笔记_55_类与数据抽象 --析构函数
复制控制
--析构函数
引言:
在构造函数中分配了资源之后,需要一个对应操作自动回收或释放资源。析构函数就是这样的一个特殊函数,它可以完成所需的资源回收,作为类构造函数的补充。
1、何时调用析构函数
撤销类对象时会自动调用析构函数:
Sales_item *p = new Sales_item; { Sales_item item(*p); //调用复制构造函数 delete p; //调用指针p的析构函数 } //调用对象item的析构函数
动态分配的对象只有在指向该对象的指针被删除时才撤销,如果没有删除指向动态对象的指针,则不会运行该对象的析构函数,对象就会一直存在,从而导致内存泄漏,而且,对象内部使用的任何资源也不会释放!
【谨记】
当对象的引用或指针超出作用域时,不会运行析构函数。只有删除指向动态分配对象的指针或实际对象(而不是对象的引用)超出作用域时,才会运行析构函数!
撤销一个容器(不管是标准库容器还是内置数组)时,也会运行容器中的类类型元素的析构函数:
{ Sales_item *p = new Sales_item[10]; vector<Sales_item> vec(p,p + sizeof(p)/sizeof(*p)); delete []p; //调用指针p的析构函数 } //调用vector的析构函数,执行每个元素的撤销
容器中的元素总是按照逆序撤销:首先撤销下标为size()-1的元素...
2、何时编写显式析构函数
析构函数常用于释放在构造函数或在对象生命期内获取的资源。
如果类需要析构函数,则他也需要赋值操作符和复制构造函数,这是一个有用的经验法则“三法则”,指的是如果需要析构函数,则需要所有这三个复制控制成员!
析构函数并不仅限于用来释放资源,一般而言,析构函数还可以执行任意操作,该操作是类设计者希望在该类对象的使用完毕之后执行的!
3、合成析构函数
与复制构造函数或赋值操作符不同编译器总会为我们合成一个析构函数,即使我们已经自己编写了析构函数。合成析构函数按对象创建时的逆序撤销每个非static成员,因此,它按成员在类中声明次序的逆序撤销成员!对于类类型的每个成员,合成析构函数调用该类成员的析构函数来撤销对象。
撤销内置类型成员或复合类型的成员没什么影响。尤其是,合成析构函数并不删除指针成员所指向的对象。
4、如何编写析构函数
分配了资源的类一般需要定义析构函数以释放那些资源。析构函数是个成员函数,它的名字是在类名字之前加上一个代字号(~),它没有返回值,没有形参。虽然可以为一个类定义多个构造函数,但只能提供一个析构函数,应用于类的所有对象。
析构函数与复制构造函数或赋值操作符之间的一个重要区别是,即使我们编写了自己的析构函数,合成析构函数仍然运行。
class Sales_item { public: ~Sales_item() { } };
撤销Sales_item类型的对象时,将会运行这个什么也不做的析构函数,它执行完毕之后,将运行合成析构函数以撤销类的成员。
//P414 习题13.12 class NoName { public: NoName():pstring(new std::string),i(0),d(0){} NoName(const NoName &rhs) { pstring = new std::string; *pstring = *(rhs.pstring); i = rhs.i; d = rhs.d; } ~NoName() { delete pstring; } private: std::string *pstring; int i; double d; };
//习题13.14 class Exmpl { public: Exmpl() { cout << "Exmpl()" << endl; } Exmpl(const Exmpl &other) { cout << "Exmpl(const Exmpl &other)" << endl; } Exmpl &operator=(const Exmpl &other) { cout << "Exmpl &operator=(const Exmpl &other)" << endl; return *this; } ~Exmpl() { cout << "~Exmpl()" << endl; } }; void fun1(Exmpl obj) { } void fun2(const Exmpl &obj) { } Exmpl fun3() { Exmpl tmp; return tmp; } int main() { Exmpl eobj; //调用默认构造函数 fun1(eobj); //调用复制构造函数,函数fun1调用完成之后,调用析构函数 fun2(eobj); //不适用复制控制 eobj = fun3(); //在函数体内 //1.调用默认构造函数 //2.调用复制构造函数 //3.调用析构函数 Exmpl *p = new Exmpl; //调用默认构造函数 /* *调用一次构造函数,然后连续三次调用复制构造函数 *将临时值复制到vector容器中的每个元素 */ vector<Exmpl> vecExp(3); Exmpl arr[5]; //或自动调用构造函数与析构函数 delete p; //调用析构函数,如果不显式的delete,则对象p永远不会释放 }