C++的一个主要目的是促进代码重用。公有继承是实现这种目标的方式之一。但不是唯一的方式。其他方法:使用本身就是另一个类的对象的类成员。例如Stock
类中包含一个string
类成员。这种方式成为包含(containment)、组合(composition)或层次化(layering)。以及使用私有或保护继承,可以实现has-a
关系。模板(Template)也是实现代码重用的一个重要手段。
包含对象成员的类
Student类,计划用一个string
类对象来表示姓名,一个valarray<double>
类来表示成绩。
这里公有派生,从valarray中、或者string类中派生出学生类就不太适合了,因为学生和姓名,学生和成绩不是is-a关系。而是has-a关系——学生有姓名,学生有成绩。方法就是包含,即创建一个包含其他对象的类。1
2
3
4
5
6class Student
{
private:
string name;
valaaray<double> scores;
};
将上述成员声明为私有的,Student
类成员函数可以使用string
类和valarray
类的公有方法来修改name
和scores
对象。对于这种情况,描述为:Student类获得其成员对象的实现,但没有继承接口。举个例子说,就是虽然Student
使用了string
类对象用作表示姓名,但Student
没有获得使用string::operator+=()
的能力。(没有用加号来让两个学生对象的姓名相连的接口),如果要实现这种功能,做法是要在Student
里,通过name
对象调用string
的方法。例如name.size();
通过scores
对象来调用valarray
方法,例如scores.max();
对has-a
关系来说,类对象不能自动获得被包含对象的接口是一件好事。例如两个string
对象可以使用+=
来实现连接的功能。但对Student
来说+=
是没有意义的。
私有继承:Student类的新版本
C++实现has-a关系的另一种方法:私有继承。使用私有继承,基类的公有成员和保护成员都将成为派生类的私有成员。这意味着:基类方法将不会成为派生类对象的公有接口的一部分,但可以在派生类的成员函数中使用它们。
1 | class Student :private std::string, private std::valarray<double> |
基类组件
因为隐式继承组件而不是成员对象将影响代码的编写。没有name
和scores
来描述对象了。而必须使用用于公有继承的技术。对于构造函数,使用类名而不是成员名。
1 | Student() :std::string("Null Student"), ArrayDb() {} |
访问基类方法
使用私有继承时,只能在派生类的方法中使用基类的方法。使用包含时,是用对象名和成员运算符”.”来调用方法,而使用私有继承时使用类名和作用域解析运算符。举下面这个方法为例:
1 | double Student::Average() const |
访问基类对象
使用作用域解析运算符可以访问基类的方法。但如果要使用基类对象本身,该如何做呢?例如,下面这段代码是使用包含*关系的Student
类有Name()
方法1
2
3
4const string & Student::Name()const
{
return name;
}
但使用私有继承时,该string对象没有名称,那么如何访问内部string对象?答案是使用强制类型转换。由于Student
类是由string
类派生的,因此可以通过强制类型转换来把Student
类转换为string
类对象。指针this来指向用来调用方法的对象,*this则为调用方法的对象本身。而且为了避免调用构造函数创建新的对象,所以创建一个引用,返回引用。
1 | const string & Student::Name()const |
私有继承的指针关系
这一行代码:1
os << "Scores of" << (const string &) stu << ":\n";
这里用的是operator<<(ostream & ,const string &);
原因:在私有继承中,未进行显示类型转换的派生类引用和指针,无法赋值给基类的引用和指针。
使用包含还是私有继承?
大多数C++程序倾向于使用包含。首先,包含易于理解。其次,继承会引起很多问题,尤其是多重继承。例如,如果某个类需要3个string类对象,那么可以包含3个独立的string成员,然而使用继承的话很麻烦,只能使用一个这样的对象(对象没有名称,难以区分)
然而,私有继承的特性比包含多。例如:派生类可以重新定义虚函数,但包含不能。例如 派生类可以访问保护成员,包含不能。
下面是上述用私有继承实现的Student类的完整代码:
1 |
|
保护继承
保护继承是私有继承的辩题。使用关键字protected
1
2
3
4class Student: protected std::string, protected std::valarray<double>
{
……
};
使用保护继承时,基类的公有成员和保护成员将成为派生类的保护成员。和私有继承一样,基类的接口在派生类中也是可用的,但在继承层次结构之外是不可用的。
当从派生类派生出另一个类时,私有继承和保护继承之间的主要差别就出来了。使用私有继承时,第三代类就不能使用基类的接口,因为基类的公有方法在派生类中是私有方法。使用保护继承时,基类的公有方法在第二代类中将变成保护的,因此在第三代类中可以使用。
各种继承小总结
特征 | 公有继承 | 保护继承 | 私有继承 |
---|---|---|---|
公有成员变成 | 派生类的公有成员 | 派生类的保护成员 | 派生类的私有成员 |
保护成员变成 | 派生类的保护成员 | 派生类的保护成员 | 派生类的私有成员 |
私有成员变成 | 只能通过基类的接口访问 | 只能通过基类的接口访问 | 只能通过基类接口访问 |
能否隐式向上转换? | 能 | 能(但只能在派生类中) | 不能 |