虚析构函数和容器继承 虚析构函数 容器与继承


在一个继承体系中,在继承链最顶端通常需要定义一个虚析构函数,即使它只是负责销毁资源而已:

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;

可以将一个派生类指针转换为基类指针,同样也可以将派生类的智能指针转换为基类的智能指针。

因为存放了基类指针类型, 因此在添加派生类对象后,并不会发生截断现象,在调用成员函数时,如果成员函数时虚函数,则依据基类指针的动态类型来执行相应的版本。