C++ 提供了几种重用代码的手段。第 13 章介绍的公有继承能够建立 is-a 关系,这样派生类可以重用基类的代码。私有继承和保护继承也使得能够重用基类的代码,但建立的是 has-a 关系。使用私有继承时,基类的公有成员和保护成员将成为派生类的私有成员;使用保护继承时,基类的公有成员和保护成员将成为派生类的保护成员。无论使用哪种继承,基类的公有接口都将成为派生类的内部接口。这有时候被称为继承实现,但并不继承接口,因为派生类不能显式地使用基类的接口。因此,不能将派生对象看作是一种基类对象。由于这种原因,在不进行显式转换的情况下,基类指针或引用将不能指向派生类对象。
还可以通过开发包含对象成员的类来重用类代码。这种方法被称为包含、层次化或组合,它建立的也是 has-a 关系。与私有继承和保护继承相比,包含更容易实现和使用,所以通常优先采用这种方式。然而,私有继承和保护继承比包含有一些不同的功能。例如,继承允许派生类访问基类的保护成员;还允许派生类重新定义从基类那里继承的虚函数。因为包含不是继承,所以通过包含来重用类代码时,不能使用这些功能。另一方面,如果需要使用某个类的几个对象,则用包含更适合。例如,State 类可以包含一组 County 对象。
多重继承(MI)使得能够在类设计中重用多个类的代码。私有 MI 或保护 MI 建立 has-a 关系,而公有 MI 建立 is-a 关系。 MI 会带来一些问题,即多次定义同一个名称,继承多个基类对象。可以使用类限定符来解决名称二义性的问题,使用虚基类来避免继承多个基类对象的问题。但使用虚基类后,就需要为编写构造函数初始化列表以及解决二义性问题引入新的规则。
类模板使得能够创建通用的类设计,其中类型(通常是成员类型)有类型参数表示。典型的模板如下:
template
class Ic{
T v;
...
public:
Ic(const T & val) : v(val) { }
...
};
其中,T 是类型参数,用作以后将指定的实际类型的占位符(这个参数可以是任意有效的 C++ 名称,但通常使用 T 和
template // same as template< class T>
class Rev { ... }
类定义(实例化)在声明类对象并指定特定类型时生成。例如,下面的声明导致编译器生成类声明,用声明中的实际类型 short 替换模板中的所有类型参数 T:
class Ic sic; // implicit instantiation
这里,类名为 Ic
使用关键字 template 声明类的特定具体化时,将发生显式实例化:
template class Ic; // explicit instantiation
在这种情况下,编译器将使用通用模板生成一个 int 具体化——Ic
可以提供显式具体化——覆盖模板定义的具体类声明。方法是以 template<>打头,然后是模板类名称,再加上尖括号(其中包含要具体化的类型)。例如,为字符指针提供专用 Ic 类的代码如下:
template <> class Ic{
char * str;
...
public:
Ic(const char * s) : str(s) { }
...
};
这样,下面这样的声明将为 chic 使用专用定义,而不是通用模板:
class Ic chic;
类模板可以指定多个泛型,也可以有非类型参数:
template
class Pals { ... };
下面的声明将生成一个隐式实例化,用 double 代替 T,用 string 代替 TT,用 6 代替 n;
Pals mix;
类模板还可以包含本身就是模板的参数:
template class CL, typename U, int z>
class Trophy { ... };
其中 z 是一个 int 值,U 为类型名,CL 为一个使用 template
类模板可以被部分具体化:
template Pals { ... };
template Pals {... };
template Pals { ... };
第一个声明为两个类型相同,且 n 的值为 6 的情况创建了一个具体化。同样,第二个声明为 n 等于 100 的情况创建一个具体化;第三个声明为第二个类型是指向第一个类型的指针的情况创建了一个具体化。
模板类可用作其他类、结构和模板的成员。
所有这些机制的目的都是为了让程序员能够重用经过测试的代码,而不用手工复制它们。这样可以简化编程工作,提供程序的可靠性。