第十四章 C++中的代码重用

C++的一个主要目标是促进代码重用。

包含对象成员的类

valarray是由头文件valarray定义,其用于处理数值
通常用于建立has-a关系的C++技术是组合(包含),即创建一个包含其他类对象的类
使用组合可以获得实现,但不获得接口,共有继承可以获得接口,可能还有实现

对于模板类我们可以使用typedef简化其描述

使用成员初始化列表来初始化包含的类对象。

私有继承

C++还有另一种实现has-a关系的途径——私有继承。使用私有继承,基类的公有成员和保护成员都将成为派生类的私有成员。基类方法不会成为派生对象公有接口的一部分,但可以在派生类的成员函数中使用它们。这种关系是has-a关系。

包含将对象作为一个命名的成员对象添加到类中,而私有继承将对象作为一个未被命名的继承对象添加到类中。我们将使用术语子对象(subobject)来表示通过继承或包含添加的对象。

使用包含时使用对象名来调用方法,而使用私有继承时,将使用类名和作用域解析运算符来调用方法

私有继承如果希望访问基类对象,可以使用强制类型转换。同样,可以通过强制类型转换来使用基类的友元函数。

在私有继承中,在不进行显式类型转换的情况下,不能将指向派生类的引用或指针赋给基类引用或指针。在多重继承情况下,不进行强制类型转换也无法确定应转换成哪个类。然而即使公有继承也必须进行强制类型抓换。

使用包含还是私有继承

使用包含更容易理解,继承会引起多个问题,尤其是使用多重继承的情况下,另外还可能需要包含类的多个子对象

通常应使用包含来实现has-a,如果新类要访问原有类的保护成员,或需要重新定义虚函数,则应使用私有继承

保护继承

保护继承是私有继承的变体,其使用关键字protected
在使用保护继承时,基类的共有成员和保护成员都将成为派生类的保护成员。和私有继承一样,基类的接口在派生类中也是可用的,但在继承层次结构之外是不可用的,当从派生类派生出另一个类时,私有继承和保护继承之间的主要区别就呈现出来了,使用私有继承时,第三代类将不能使用基类的接口,这是因为基类的共有方法在派生类中将变成私有方法,使用保护继承时,基类的共有方法在第二代类中将变成受保护的,因此第三代类可以使用它们。隐式向上转换(implicit upcasting)意味着无需进行显式类型转换,就可以将基类指针或引用指向派生类对象。如下表
| 特征 | 共有继承 | 保护继承 | 私有继承 |
| —- | —- | —- | —- |
| 公有成员变成 | 派生类的公有成员 | 派生类的保护成员 | 派生类的私有成员 |
| 保护成员变成 | 派生类的保护成员 | 派生类的保护成员 | 派生类的私有成员 |
| 私有成员变成 | 只能通过基类接口访问 | 只能通过基类接口访问 | 只能通过基类接口访问 |
| 能否隐式向上转换 | 是 | 是(但只能在派生类中) | 否 |

使用using重新定义访问权限

使用保护派生或私有派生时,假设要使基类的方法在派生类外可用,方法之一是定义一个使用该基类方法的派生类方法,如

1
2
3
4
double Student::sum() const
{
return std::valarray<double>::sum();
}

另一种方法是,将函数调用包装在另一个函数调用中,即使用一个using声明来指出派生类可以使用基类特定的基类成员,即使采用的是私有派生,注意,using声明只使用成员名,没有圆括号,函数特征标和返回值,这使得所有成员名的方法都可用,其只适用于继承,不适用于包含。

1
2
3
4
5
6
class Student: private std::string, private std::valarray<double>
{
public:
using std::valarray<double>::min;
using std::valarray<double>::max;
};

多重继承