首先,来看两种类的定义:
这是包含:
class Student
{
private:
typedef std::valarray<double> ArrayDb;
std::string name;
ArrayDb scores;
std::ostream & arr_out(std::ostream & os)const;
public:
Student():name("Null Student"),scores(){}
explicit Student(const std::string & os):name(os),scores(){}
explicit Student(int n):name("Nully"),scores(n){}
Student(const std::string &s,int n):name(s),scores(n){}
Student(const std::string &s,const ArrayDb &a):name(s),scores(a){}
Student(const char *str,const double *pd,int n):name(str),scores(pd,n){}
~Student(){}
double Average()const;
const std::string &Name()const;
double &operator[](int i);
double &operator[](int i)const;
friend std::istream & operator >>(std::istream &is,Student &stu);
friend std::istream & getline(std::istream & is,Student & stu);
friend std::ostream & operator<<(std::ostream & os,const Student & stu);
};
这是私有继承的:
class Student:private std::string,private std::valarray<double>
{
private:
typedef std::valarray<double> ArrayDb;
std::ostream & arr_out(std::ostream & os)const;
public:
Student():std::string("Null Student"),ArrayDb(){}
explicit Student(const std::string &s):std::string(s),ArrayDb(){}
explicit Student(int n):std::string("Nully"),ArrayDb(n){}
Student(const std::string & s, int n):std::string(s),ArrayDb(n){}
Student(const std::string &s,const ArrayDb & a):std::string(s),ArrayDb(a){}
Student(const char * str,const double * pd,int n):std::string (str),ArrayDb(pd,n){}
~Student(){}
double Average()const;
double & operator[](int i);
double & operator[](int i)const;
const std::string & Name()const;
friend std::istream & operator>>(std::istream & is,Student & stu);
friend std::istream & getline(std::istream & is,Student & stu);
friend std::ostream & operator<<(std::ostream & os,const Student & stu);
};
使用私有继承,雷将继承实现。例如,从String类派生出Student类,后者将有一个String类组件,可用于保存字符串。另外,Student方法可以使用String方法来访问string组件。
包含将对象作为一个命名的成员对象添加到类中,而私有继承将对象作为一个未被命名的继承对象添加到类中。
因此,私有继承提供的特性与包含相同:获得实现,但不获得接口。所以,私有继承也可以用来实现has-a关系。
在私有继承重新定义Student类中,新的累不需要私有数据,因为两个基类已经提供了所需要的所有的数据成员。包含版本提供了两个被显式命名的对象成员,而私有继承提供了两个无名称的子对象成员。这事两种方法的第一个主要区别。
由于包含显式的命名了数据成员name,因此可以这样定义构造函数:
Student(const std::string &s,const ArrayDb &a):name(s),scores(a){}
但是对于私有继承来说,只能这样来定义:
Student(const std::string & s, int n):std::string(s),ArrayDb(n){}
这是两者的第二个区别。
对于访问基类的方法时,包含是这样定义的:
double Student::Average()const
{
if(scores.size()>0)
return scores.sum()/scores.size();
else
return 0;
}
而私有继承和它差不多,只不过由于没有了显式定义的数据成员,因此定义如下:
double Student::Average()const
{
if(ArrayDb::size()>0)
return ArrayDb::sum()/ArrayDb::size();
else
return 0;
}
从上面的例子,可以很明显的看出,使用包含试将使用对象名来调用方法,而使用私有继承将使用类名和作用域解析运算符来调用方法。
对于访问基类对象时:
使用作用域解析运算符可以访问基类的方法,但如果要使用基类对象本身,该如何做呢?例如,Student类的包含版本实现了Name()方法,它返回string对象成员name;但使用私有继承时,该string对象没有名称,那么,Student类的代码如何访问内部string对象呢?
答案是使用强制类型转换。由于Student类是从string类派生而来的,因此可以通过强制转换,将Student对象转换为string对象;结果为继承而来的string对象。我前面介绍过,指针this指向用来调用方法的对象,因此*this为用来调用方法的对象,在这个例子中,为类型为Student的对象。为了避免调用构造函数创建新的对象,可使用强制类型转换来创造一个引用:
<span style="font-family:SimSun;">const string & Student::Name()const
{
<span style="white-space:pre"> </span>return (const string &) *this;
}</span>
上述方法返回一个引用,该引用指向用于调用该方法的Student对象中的继承而来的string对象。
访问基类的友元函数:
用类名显式的限定函数名不适合于友元函数,这事因为友元函数不属于类。然而,可以通过显式地转换为基类来调用正确的函数。例如:
<span style="font-family:SimSun;">ostream & operator<<(ostream & os,const student & stu)
{
os<<"scores for "<<(const string &)stu<<":/n";
...
}</span>
如果plato是一个student对象,则cout<<plato;将调用上述函数,stu将是指向plato的引用,而os将是指向cout的引用。下面的代码:<span style="font-family:SimSun;font-size:10px;">os<<"scores for "<<(const string &)stu<<":/n";</span>
显示地将stu转换为string对象引用,这与operator<<(ostream &,const string &)函数匹配。
引用stu不会自动的转换为string引用。根本原因在于,在私有继承中,在不进行显示类型转换的情况下,不能将指向派生类的引用或指针赋给基类引用或指针。
(即使这个例子中使用的是公有继承,也必须使用显示类型转换。原因之一是,如果不使用类型转换,代码(so<<stu)将与友元函数原型匹配,从而导致递归调用。另一个原因是,该类使用多重继承,编译器无法确定应该转换成那个基类,如果两个基类都提供了函数operator<<()的话。)
那么,使用包含还是私有继承呢?
对于使用包含来说,它易于理解。类声明中包含表示被包含类的显示命名对象,代码可以通过名称引用这些对象,而使用继承将使关系更抽象。其次,继承会引起许多问题,尤其从多个基类继承时,可能必须处理许多问题,例如包含同名方法的独立的基类,或共享祖先的独立基类。而使用包含则不大可能会遇到这样的麻烦。另外,包含能够包括多个同类的子对象。如果某个类需要3个string对象,可以使用包含声明3个独立的string成员。而继承则只能使用一个这样的对象(当对象都没有名称时,将难以区分)。
但私有继承所提供的特性却比包含多。例如,假设类包含保护成员(可以是数据成员,也可以是成员函数),则这样的成员在派生类中是可用的,但在继承层次结构外是不可用的。如果使用组合将这样的类包含在另一个类中,则后者将不是派生类,而是位于继承层次结构之外,因此不能访问保护成员。但通过继承得到的将是派生类,因此它能够访问保护成员。
另一种需要使用私有继承的情况是需要重新定义虚函数。派生类可以重新定义虚函数,但包含类不能。使用私有继承,重新定义的函数将只能在类中使用,而不是公有的。
通常,使用包含来建立has-a关系;如果新类需要访问原有类的保护成员,或需要重新定义虚函数,则应使用私有继承。