私有继承是C++中实现has-a关系的另一种途径。使用私有继承,基类的公有成员和保护成员都将称为派生类的私有成员。换言之,基类方法不会成为派生对象公有接口的一部分,只可以在派生类的成员函数中使用。
- 公有继承:基类公有方法将成为派生类的公有方法;派生类继承基类的接口,是is-a关系。
- 私有继承:基类公有方法将成为派生类的私有方法;派生类不继承寄了的接口,是has-a关系。
私有继承提供的特性与包含相同;获得实现,但不获得接口。
class Student : private std::string, private std::valarray<double> {
public:
...// func body
}
1 初始化基类组件
项目 | 包含 | 私有继承 |
---|---|---|
成员对象 | 提供了被显示命名的对象成员 | 提供了无名称的子对象成员 |
成员列表初始化 | 使用name(str) | 使用std::string(str) |
实现关系 | has-a | has-a |
//use object names for containment
class Student {
private:
typedef std::valarray<double> ArrayDb;
std::string name;
ArrayDb score;
};
Student(const char* str, const double* pd, int n)
: name(str), score(pd, n) {}
//use class name for inheritance
class Student : private std::string, private std::valarray<double> {
public:
...// func body
}
Student(const char* str, const double* pd, int n)
: std::string(str), ArrayDb(pd, n) {}
代码中第二种方法,class Student继承了类std::string和std::valarray<double>,所以string和valarray是Student类的基类。
2 访问基类的方法
私有继承只能在派生类的方法中使用基类的方法。
包含使用对象来调用方法:
double Student::Average() const {
if (score.size() > 0)
return score.sum() / score.size();
else
return 0;
}
私有继承使用类名和作用域解析运算符来调用基类的方法:
double Student::Average() const {
if (ArrayDb::size() > 0)
return ArrayDb::sum() / ArrayDb::size();
else
return 0;
}
通过上面两个例子可以看到:
- 使用包含时将使用对象名来调用方法;
- 使用私有继承时将使用类名和作用域解析运算符来调用方法。
3 访问基类对象本身
通过使用作用域解析运算符可以访问基类的方法,那么若要访问基类本身该怎么办?
可以通过强制类型转换实现访问基类本身。
指针 this 指向用来调用方法的对象,*this 为用来调用方法的对象。
const string &Student::Name() const {
return (const string &) *this; //将对象强制转换为基类string
}
4 访问基类友元函数
首先,友元函数不是不属于类,所以通过使用类名显示地限定函数名不适合调用友元函数。可以通过显示地转换为基类来调用正确的函数(如下)。
friend std::ostream &operator<<(ostream &os, const Student &stu);
ostream &operator<<(ostream &os, const Student &stu) {
//显示转换为基类进行调用
os << "Scores for " << (const string &) stu << ":\n;
...
}
在私有继承中,未进行显示类型转换的派生类引用或指针,无法赋值给基类的引用或指针。
5 包含和私有继承的选择
虽然两种方式都可以使用,都建立has-a关系,且可以完成同样的工作。但大多数C++程序员倾向于使用包含。
包含
- 包含简单,类声明中包含表示被包含类的显示命名对象,可以通过名称引用对象。包
- 含能够包括多个同类的子对象(例如可以声明3个独立的string)。
- 对于保护成员不能访问。
- 无法重新定义虚函数。
私有继承
- 继承使得关系更加抽象,同时当从多个基类继承时可能会引发一系列问题,需要特殊处理。
- 继承只能使用一个这样的对象。
- 继承具有更多的特性,当含有保护成员时,派生类可以使用。
- 可以重新定义虚函数。
通常,应使用包含来建立has-a关系;如果新类需要访问原有类的保护成员,或需要重新定义虚函数,应使用私有继承。