15.11.3 创建基于对象的继承
本书中,在对容器类Stack和Stash的描述中,有一点是重复出现的,这就是“所有权问题”。负责对动态创建(使用new)的对象进行delete调用的称为“所有者”。在使用容器时的问题是,它们需要足够的灵活性用来接收不同类型的对象。为了做到这一点,容器使用void指针,因此它们并不知道所包容对象的类型。删除一个void指针并不调用析构函数,所以容器并不负责清除它的对象。
在第14章的例子InheritStack.cpp中提出了一种解决办法,从Stack继承出一个仅可以接收和生成string指针的类。所以它知道它只包容了指向string对象的指针,因此它可以正确地删除它们。这是一个不错的解决办法,但是它要求我们要为想在容器中容纳的每一种类型都派生出一个新类。(虽然现在看起来有点冗余,但在第16章中介绍过模板后,它运行得相当不错。)
问题是我们希望容器可以容纳更多的类型,但我们不想使用void指针。另外一种解决方法是使用多态性,它通过强制容器内的所有对象从同一个基类继承而来。这就是说,容器容纳了具有同一基类的对象,并随后调用虚函数—特别地,我们可以调用虚析构函数来解决所有权问题。
这种解决方法使用单根继承(singly-rooted hierarchy)或基于对象的继承(object-based hierarchy)(这是因为继承的根类通常称为“对象”)。可以看到使用单根继承还有其他一些优点。事实上,除了C++,每种面向对象的语言都强制使用这样的体系—当创建一个类时,都会直接或间接地从一个公共基类中继承出它,这个基类是由该语言的创建者生成的。C++中认为,强制地使用这个公共基类会引起太多的开销,所以便没有使用它。然而,我们可以在自己的项目中选择是否使用它,在本书的第2卷中将进一步讨论这个主题。
为了解决所有权问题,可以创建一个相当简单的类Object作为基类,它仅包含一个虚析构函数。Stack于是可以容纳继承自Object的类。
通过把所有的东西放在头文件中来简化问题,纯虚析构函数(所要求的)的定义以内联形式置于头文件中,并且pop()也是内联的(对于内联形式来说,它可能太大了)。
Link对象现在是指向Object指针,而不是void指针,并且Stack也将仅仅接收和返回Object指针。现在,Stack更具有灵活性,因为它容纳了大量不同的类型,而且也可以消除被置于Stack中的任一对象。新的限制(在第16章中,当对这个问题运用模板时,将不具有这个限制)是置于Stack中的所有内容都必须继承自Object。如果新建一个类,这还是可行的,但如果已经有了一个类(例如string),并且希望把它置于Stack中,又会如何呢?这种情况下,新类必须具备string和Object的特点,即它必须继承自这两个类。这称之为多重继承(multiple inheritance),在本书第2卷(可从www.BruceEckel.com处下载)中有一整章是关于这个主题的。当我们阅读该章时,将会看到多重继承是非常复杂的,应尽量少用这一功能。
然而,在这里,所有的一切都是很简单的,所以无需考虑多重继承的任何缺点。
虽然这个代码段与Stack以前的测试程序版本很相似,但我们注意到仅有10个元素从栈中弹出,这意味着还保留了一些对象。因为Stack知道它包容了Object,并且析构函数可以正确地把它们清除掉。因为MyString对象在它们被清除时打印信息,所以我们可从程序的输出中知道这一点。
创建包容Object的容器是一种合理的方法—如果使用单根继承(由于语言本身或需要的缘故,强制每个类继承自Object)。这时,保证一切都是一个Object,因此在使用容器时并不是十分复杂。然而,在C++中,不能期望这适用于每个类,所以如果有多重继承会出现问题。在第16章中会看到模板可以使用更简单、更灵巧的方法来处理这个问题。