第 14 章 C++ 中的代码重用
14.1 包含对象成员的类
类初始化列表中有多个项目时,初始化的顺序为在类中的声明顺序而不是列表顺序。
14.2 私有继承
使用私有继承,基类的所有公有成员和保护成员都将成为派生类的私有成员,基类方法将不再成为派生对象公有接口的一部分,但可以在派生类的成员函数中使用它们。私有继承获得实现,但不获得接口。
可以通过私有继承多个基类实现 has-a 关系,类似于使用成员变量。
私有继承可以提供两个无名的基类成员。
公有继承中使用构造函数的初始化成员列表 Student(const string &name): m_name(name) { ... };
,而在私有继承中则使用类名代替成员名 Student(const string &name): std::string(name) { ... };
。
使用包含时使用对象名调用方法,私有继承则使用类名和作用域解析运算符调用方法。
使用强制类型转换 const String & Student::Name() const { return (const string &) *this; }
来获取基类对象的引用。
大多数时候应该使用包含,如果新的类需要访问原有类的保护成员或重新定义虚函数,则应该使用私有继承。
使用保护继承,基类的所有公有成员和保护成员都将成为派生类的保护成员,基类的接口将仅在派生类中可用。
为了使基类公有成员在保护继承或私有继承的派生类外可用,可用在派生类中重新声明同名方法并实现,或使用 using
声明公有函数。
14.3 多重继承
当继承多个具有相同祖先的基类时会出现问题。
从 Singer
和 Waiter
中派生出类 SingerWaiter
。将派生类对象指针赋给基类指针将出现问题,即 SingerWaiter sw; Worker *w = &sw;
会出现二义性。这是应当使用强制类型转换,即 Worker *w = (Singer *) &sw;
以指定类型。
将类声明为虚基类可以使派生的对象只继承一个基类对象,声明为 public Singer: virtual public Worker { ... };
即可,但这样做会要求修改已有的代码。
多重继承时,构造函数可能会出现基类构造函数经过多条途径被多次调用,这时编译器将调用该参数的默认构造函数,否则需要在初始化列表中单独显式调用基类构造函数。
多重继承还可能导致函数调用的二义性,需要使用作用域解析运算符指定具体的基类函数。
14.4 类模板
以 template <class T>
开头的类声明为模板类,每个函数头也应该以相同的模板声明开头,类限定符也需要加上 <T>
。
使用内置类型或类对象用作实例化的类型名称是可行的,指针同样也可行,但可能需要修改类模板进行移动指针的操作。
使用模板头 template <class T, int n>
将同时指出一个非类型参数,在实例化时 n
必须为常量表达式。可以使用多个类型参数 template <class T1, class T2>
或设置模板类的默认类型 template <class T1, class T2 = int>
。
声明类模板对象 ArrayTP<int, 10> stuff;
时会实现隐式实例化,使用关键字 template
并指出类型 tmeplate class ArrayTP<int, 10>;
时会实现显式实例化。
当需要为特定类型的类模板进行修改时,可以创建显式具体化。首先声明通用类模板 template <class T> class SortedArray{ ... };
,再进行具体化 template <> class SortedArray<const char *> { ... };
。当请求 SortedArray<const char *>
时将使用具体化而不是通用模板。
首先声明通用类模板 template <class T1, class T2> class Pair { ... };
,然后进行部分具体化 template <class T1> class Pair<T1, int> { ... };
,当指定所有类型将会编变成显式具体化。
模板还可以用于结构、类或模板类的成员,也可以用作模板本身的参数。模板类声明也可以有友元函数。
使用 template<class T> using arrtype = std::array<T, 12>;
为模板提供别名。