【Effection C++】继承与面向对象设计
条款38:通过复合塑模出has-a或“根据某物实现出”
复合(composition)是类型之间的一种关系,当某种类型的对象内涵它种类型的对象,便是这种关系。复合意味着出has-a(有一个)或is-implemented-in-terms-of(根据某物实现出)。
has-a 复合关系
class Person
{
private:
// has a string类型的变量
string Name;
// has a PhoneNumber对象
PhoneNumber HomeNumber;
// has a TelephoneNumber对象
PhoneNumber TelephoneNumber;
};
对于这个例子,我们就可以说Person has a Name,Person has a HomeNumber,Person has a TelephoneNumber,
is-implemented-is-of 复合关系
在这里拿出来书上的例子,以STL中的list来实现新版本的一个set,注重空间而不注重效率。
- 无法让你新建的set继承STL中的list来实现功能,因为这无法满足public继承是一种is-a的关系。显然set不是一个list。
- 这里考虑本条款中的复合关系,显然我们可以通过list来实现set。
示例代码如下:
template<typename T>
class Set
{
public:
bool member(const T& item)const
{
return find(rep.begin(),rep.end(),item) != rep.end();
}
void insert(const T& item)
{
if(!member(item))
rep.push_back(item);
}
void remove(const T& item)
{
typename list<T>::iterator it = find(rep.begin(),rep.end(),item);
if(it != rep.end())
rep.erase(it);
}
size_t size()const
{
return rep.size();
}
void print()
{
list<T>::iterator it = rep.begin();
for(;it != rep.end();++it)
cout<<*it<<"\t"<<endl;
}
private:
list<T> rep; //通过list来实现set
};
条款39:明智而审慎的使用private继承
private继承的规则为:
1. 如果classes之间的继承关系是private,编译器不会自动将一个derived class对象转换为一个private.
2. 由private base class继承而来的所有成员,在derived class中都会变成private属性,纵使它们在base class中原本是protected或public属性。
private继承意味着implemented-in-terms-of,根据某物实现出。
private继承意味着implemented-in-terms-of,根据某物实现出,而条款38也指出复合的意义也是如此。那么应该如何在两者之间进行取舍呢?
答:尽可能的使用复合,只有在必要时候才是有private继承,即主要是当protected成员,和/或 需要重新定义其中的virtual成员函数,还有一种情况就是,如果相应的base class不带有任何数据时候,无non-static成员变量,无virtual函数,也无virtual base classes。而代码又比较在意内存空间的大小,就可以使用继承方式来完成操作。
这主要是由于空白基类最优化(empty base optimization-EBO 或 empty base classopimization-EBCO)的作用。在空基类被继承后由于没有任何数据成员,所以子类优化掉基类所占的1 byte。EBO并不是c++标准所规定必须的,但是大部分编译器都会这么做。
要注意EBO一般只在单一继承下而非多重继承下才可行。
最后:
- private继承意味着is-implemented-in-terms of(根据某物实现出)。它通常比复合(composition)的级别低。但是当derived class需要访问 protected base class的成员,或者需要重新定义继承而来的virtual函数时,这个设计是合理的。
- 和复合(composition)不同,private继承可以造成空白基类最优化,它虽然不是C++标准所规定的,但是几乎在所有编译器上都实现了。这对致力于对象尺寸最小化的程序库开发者而言,可能很重要。