虚析构函数
在一个继承体系中,在继承链最顶端通常需要定义一个虚析构函数,即使它只是负责销毁资源而已:
class Quete { public : // virtual ~Quete() = default; }; class Bulk_quote : public Quote { public : ~Bulk_quote() = default; };
在上面的继承体系中, 基类定义了一个虚析构函数,是为了可以动态分配继承体系中的对象 :
Quote* itemQ = new Quote; delete itemQ; // 调用Quote的析构函数 itemQ = new Bulk_quote; delete itemQ; // 调用Bulk_quote的析构函数
上面的代码里, delete一个动态申请的指针类型, 会进行动态绑定。编译器会依据指针的动态类型来执行相应版本的析构函数:
在delete一个Quote类型指针时, itemQ的动态类型是Quote,因此会执行Quote的析构函数;
在delete一个Bulk_quote类型指针时, itemQ的动态类型是Bulk_quote,因此会执行Bulk_quote的析构函数。
虚析构函数机制能够确保我们在delete一个基类指针时能运行正确的析构函数版本。
★ 如果基类的析构函数不是虚函数, 则delete一个指向派生类对象的基类指针将会产生未定义的行为。
对于一个不存在继承体系的类来说, 它需要一个虚构函数则它通常也需要定义自己的拷贝和赋值操作。 对于处于继承体系中的基类来说不遵循此规则, 它通常来说需要将自己的虚构函数定义为虚函数, 它为了成为一个虚函数而令内容为空, 因此无法推断它是否还需要拷贝或赋值操作。
虚析构函数也会阻止合成移动操作。如果类需要移动操作, 需要自己定义移动操作。
容器与继承
容器继承典型的例子就是,在一个类型为基类类型的容器, 要同时存放基类对象和派生类对象,一般的做法是:
std::vector<Quote> basket; // 插入一个基类对象 basket.push_back(Quote("0-201-82470-1", 50)); // 插入一个派生类对象, 但此派生类对象的派生类部分会被"切掉" basket.push_back(Bulk_quote("0-201-54848-8", 50, 10, 0.25));
basket是一个存放基类Quote的vector容器, 添加完一个基类对象后, 继续添加一个派生类对象, 将一个派生类对象赋值给基类对象会"切掉"派生类部分,为了存储一个派生类对象, 做法是存放基类的指针类型,使用智能指针则是更好的选择:
std::vector<std::shared_ptr<Quote>> basket; basket.push_back(make_shared<Quote>("0-201-82470-1", 50)); basket.push_back(make_shared<Bulk_quote>("0-201-54848-8", 50, 10, 0.25)); std::cout << basket.back()->net_price(15) << std::endl;
可以将一个派生类指针转换为基类指针,同样也可以将派生类的智能指针转换为基类的智能指针。
因为存放了基类指针类型, 因此在添加派生类对象后,并不会发生截断现象,在调用成员函数时,如果成员函数时虚函数,则依据基类指针的动态类型来执行相应的版本。