Chapter14 C++中的代码重用
类的成员本身是另一个类的对象,称为包含(containment)、组合(composition)或层次化(layering)。
通常,包含、私有继承和保护继承用于实现has-a的关系,即新的类将包含另一个类的对象。多重继承使得能够使用两个或更多的基类派生出新的类,将基类的功能组合在一起。
14.1 包含对象成员的类
14.1.1 valarray类简介
valarray类是由头文件valarray支持的,它被定义为一个模板类,以便处理不同的数据类型。模板特性意味着声明对象时,必须指定具体的数据类型。因此,使用valarray类来声明一个对象时,需要在标识符valarray后面加上一对尖括号,并在其中包含所需的数据类型,如:valarray<int> test
。
使用公有继承时,类可以继承接口,可能还有实现(基类的纯虚函数提供接口,但不提供实现),获得接口时is-a关系的组成部分。而使用组合,类可以获得实现,但不能获得接口。不继承接口是has-a关系的组成部分。
使用explicit防止单参数构造函数的隐式转换,使用const限制方法修改数据,原因在于:在编译阶段出现错误优于在运行阶段出现错误。
对于继承的对象,构造函数在成员初始化列表中使用类名来调用特定的基类构造函数,对于成员对象,构造函数则使用成员名。
C++要求在构建对象的其他部分之前,先构建对象的所有成员对象,如果省略了初始化列表,C++将使用成员对象所属类的默认构造函数。当初始化列表中包含多个项目时,这些项目被初始化的顺序为它们被声明的顺序,而不是它们在初始化列表中的顺序。
14.2 私有继承
使用私有继承,基类的公有成员和保护乘员都将称为派生类的私有成员。这意味着基类方法将不会成为派生类对象公有接口的一部分,但可以在派生类的成员函数中使用它们。
使用公有继承,基类的公有方法将称为派生类的公有方法,派生类继承基类的接口,这是is-a关系的一部分;使用私有继承,基类的公有方法将称为派生类的私有方法,派生类不继承基类的接口,这种不完全继承是has-a关系的一部分。
包含将对象作为一个命名的成员对象添加到类中,而私有继承将对象作为一个未被命名的继承对象添加到类中。我们使用术语子对象(subobject)来表示通过继承或包含添加的对象。(私有继承提供的特性和包含相同:获得实现,但捕获的接口)。
使用多个基类的继承被称为多重继承(multiple inheritance, MI)。
使用私有继承时,只能在派生类的方法中使用基类的方法,然而私有继承使得能够使用类名和作用域解析运算符来调用基类的方法。(使用包含时将使用对象名来调用方法,而是用私有继承时建更实用类名和作用域解析运算符来调用方法)
在私有继承中,在不进行显式类型转换的情况下,不能将指向派生类的引用或指针赋给基类引用或指针。
类声明中包含表示被包含类的显式命名对象,代码可以通过名称引用这些对象,而使用继承将使关系更抽象。然而,私有继承提供的特性比包含多。例如,假设类包含保护成员,则这样的成员在派生类中是可用的,但在继承层次结构外是不可用的,如果使用组合将这样的类包含在另一个类中,后者位于继承层次结构之外,不能访问保护成员。(通常,应使用包含来建立has-a关系;如果新类需要访问原有类的保护成员,或需要重新定义虚函数,则应使用私有继承)
14.2.1 保护继承
保护继承是私有继承的变体。使用保护继承时,基类的公有成员和保护成员都将称为派生类的保护成员。和私有继承一样,保护继承中基类的接口在派生类中也是可用的,但在继承层次结构之外是不可用的。使用私有继承时,第三代类将不能使用基类的接口,这是因为基类的公有方法在派生类中编程私有方法;使用保护继承时,基类的公有方法在第二代中将变成受保护的,因此第三代派生类可以使用它们。
14.2.2 使用using重新定义访问权限
使用保护派生或私有派生时,基类的公有成员建更成为保护成员或私有成员。若要让基类的方法在派生类外可用,方法之一是定义一个使用该基类方法的派生类方法。例如,假设希望Student类能够使用valarray类的sum()方法,可以在Student类的声明中声明一个sum()方法,然后像下面这样定义该方法:
1 | double Student::sum() const{ |
这样Student对象便能够调用Student::sum(),后者进而将valarray<double>::sum()方法应用于被包含的valarray对象。
另一种方法是,将函数调用包装在另一个函数调用中,即使用一个using声明来指出派生类可以使用特定的基类成员,即使采用的是私有派生。例如,假设希望Student类能够使用valarray类的sum()方法,可以在Student类的声明的公有部分中加入如下using声明:
1 | class Student : private std::string, private std::valarray<double> |
using声明只是用成员名——没有圆括号、函数特征标和返回类型。
14.3 多重继承
MI描述的是有多个直接基类的类。与单继承一样,公有MI表示的也是is-a关系,私有MI和保护MI可以表示has-a关系。
虚基类使得从多个类派生出的对象只继承一个基类对象。
对于非虚基类,唯一可以出现在初始化列表中的构造函数是即时基类构造函数。
C++在基类是虚的时,禁止信息通过中间类自动传递给基类。
如果类有间接虚基类,则除非只需使用该虚基类的默认构造函数,否则必须显式地调用该虚基类的某个构造函数。
- 当类通过多条虚途径和非虚途径继承某个特定的基类时,该类将包含一个表示所有的虚途径的基类子对象和分别表示各条非虚途径的多个基类子对象
- 派生类中的名称优先于直接或简介祖先类中的相同名称(虚二义性规则与访问规则无关)
如果一个类从两个不同的类那里继承了两个同名的成员,则需要在派生类中使用类限定符来区分它们。
当派生类使用关键字virtual来指示派生时,基类就成为了虚基类:
1 | class marketing: public virtual reality{...}; |
从虚基类的一个或多个示例派生而来的类将只继承一个基类对象。为实现这种特征,必须满足其他要求:
- 有间接虚基类的派生类包含直接调用间接基类构造函数的构造函数,这对于间接非虚基类来说时非法的
- 通过优先规则解决名称二义性。
14.4 类模板
由于模板不是函数,它们不能单独编译。模板必须与特定的模板实例化请求一起使用。
模板代码不能修改参数的值,也不能使用参数的地址。
实例化模板时,用作表达式参数的值必须是常量表达式。
虽然可以为类模板参数提供默认值,但不能为函数模板参数提供默认值。然而,可以为两种模板中的非类型参数提供默认值。
14.4.1 模板的具体化
隐式实例化、显式实例化和显式具体化统称为具体化。
隐式实例化
声明一个或多个对象,指出所需的类型,而编译器使用通用模板生成具体的类定义:
1
ArrayTP<int, 100> staff;// 隐式实例化
编译器在需要对象之前,不会生成类的隐式实例化。
显式实例化
当使用关键字template并指出所需类型来声明类时,编译器将生成类声明的显式实例化。声明必须位于模板所在的名称空间中。
1
template class ArrayTP<string, 100>; //生成ArrayTP<string, 100> 类
显式具体化
显式具体化是特定类型(用于替换模板中的泛型)定义。格式如下:
1
tempalte <> class Classname<specialized-type-name>{...};
部分具体化
即部分限制模板的通用性。例如:
1
2tempalte <class T1,class T2> class Pair{...};//通用模板
template<class T1> class Pair<T1, int>{...};//部分具体化如果指定所有的类型,则<>内将为空,这将导致显式具体化。
如果有多个模板可供选择,编译器将使用具体化程度最高的模板。
14.4.2 模板类和友元
模板类的非模板友元函数
在模板类中将一个常规函数声明为友元:
1
2
3
4
5
6
7template <class T>
class HasFriend
{
public:
friend void counts();
...
};模板类的约束模板友元函数
友元函数本身成为模板。
(1)在类定义前声明每个模板函数。
1
2template <typename T> void counts();
template <typename T> void report(T &);(2)在函数中再次将模板声明为友元。
1
2
3
4
5
6template <typename TT>
class HasFriendT{
...
friend void counts<TT>();
friend void reprot<>(HasFriendT<TT> &);
}模板类的非约束模板友元函数
通过在类内部声明模板,可以创建非约束友元函数,即每个函数具体化都是每个类具体化。对于非约束友元,友元模板类型参数与模板类类型参数是不同的。
1
2
3
4
5template <typename T>
class ManyFriend{
...
template<typename C, typename D> friend void show2(C &, D &);
}
C++11允许将语法using =用于非模板,用于非模板时,何种语法与常规的typedef等价。如:
1 | typedef const char * pc1; |