Effective C++ 规则39:明智而谨慎的使用private继承

class Person { //... };
class Student : private Person { //... };

void eat(const Person& p);
void study(const Student& s);

void main()
{
	Person p;
	Student s;
	eat(p);
	eat(s);
}

        这个时候,eat(s)就会报错,提示不允许对不可访问的基类Person进行转换。所以,可以看出,如果采用 private 继承:
       1)编译器不会自动将一个 derived class 转换为一个 base class;
       2)原来在 base class 中的所有 protected or public 成员,被 private 继承到 derived class 后,在derived class 中都会变成 private 属性;

  • private 继承意味着 is-implemented-in-terms-of
  • 如果 class D 以 private 方式继承了 class B,你的用意应该是希望使用 class B 中已经实现了的某些功能,也就是说,D 对象是根据 B 对象实现而来的,除此之外,没有其他意图。

        前面一个条款说到复合(composition)也有 is-implemented-in-terms-of 的含义。那就是是选用 composition 还是 private 继承呢?答案是尽量使用 compostion,必要时才使用 private 继承。必要的情况有两种:

        1)当有 protected 成员或者 virtual 函数牵扯进来时

        假设现在有一个Widget类,我们需要让这个类记录每个成员函数的被调用次数,然后在运行期间周期性的审查记录的信息。为此,我们需要设定一个计时器。而且我们发现在现有的库函数中,Timer class 可以满足需求:

class Timer
{
public:
	explicit Timer(int tickFrequency);
	virtual void onTick() const;
	//...
};

        其中的 virtual onTick() 函数就定义的时钟每滴答一次,就需要执行的动作,非常符合我们的需求。此时,为了让 Widget 类重写 Timer 内的 virtual 函数,Widget 必须继承 Timer。public 继承在此时并不恰当,因为 Widget is not a Timer。采用 public 继承,会让 Widget 继承 Timer 的不必要的接口,容易造成客户不正确使用 Widget 接口。所以此时,我们必须以 private 形式继承 Timer:

class Widget : private Timer
{
private:
	virtual void onTick() const;
};

        通过 private 继承,既能够重新定义 virtual onTIck() 函数,又能够使得原有的Timer class 中的 public 接口隐藏起来,不对客户开放,保持了接口的不变性。 
        上面的设计已经可以较好的达到功能需求,但是别忘了,我们说过,如果 private 继承非必要,尽量采用 composition 的方式实现。对于上面的例子,也可以采用 composition 方式实现:

class Widget
{
private:
	class WidgetTimer : public Timer
	{
	public:
		virtual void onTick() const;
		//...
	};
	
	WidgetTimer timer;
	//...
};

        这种设计虽然复杂一些,但是可以有两个理由让你选择 composition 方式:

  • 如果你想设计 Widget ,使得它拥有 derived classes,但是你同时又想阻止 derived class 重新定义 onTick。如果 Widget 继承了 Timer,即使是 privete 继承,上面的想法也不能够实现。但是如果 WidgetTimer 是 Widget 内部的一个 private 成员并继承了 Timer,derived Widget class 将无法使用 WidgetTimer,当然也就无法继承或重新定义它的 virtual 函数。
  • 降低了编译依存性(见这篇文章)。如果 Widget 继承了 Timer,那么必须 #include "Timer.h",那么 Widget 所在文件和 Timer.h 文件就存在编译依赖性。但如果采用 composition 方式,WidgetTimer 可以移出到 Widget 之外而 Widget 内部含有一个指向 WidgetTimer 的指针,Widget 可以只带一个 WidgetTimer 的声明就好了,不需要再 #include 任何与 Timer 有关的东西。

        2)面对大小为零的独立对象时

        如果现在有一个不含有数据成员的 empty class 和一个含有它的其他类:

class Empty { };
class HoldsAnInt 
{
private:
	int x;
	Empty e;
};

        你会发现,sizeof(HoldsAnInt) > sizeof(int),一个不含有数据成员的类居然还是需要内存;这是因为对于大多数编译器,在面对大小为 0 的独立(非附属)对象时,C++ 官方要求在背后安插一个 char 类型数据到空对象中。所以你会发现,sizeof(Empty) = 1。同时呢,由于对齐要求,HoldsAnInt 类还会加上一些 padding,导致最后 sizeof(HoldsAnInt ) 可能就等于 8 了。
但是,如果你继承一个 empty class,上面所说的就不成立了:

class HoldsAnInt : private Empty
{
private:
	int x;
};

        这个时候,几乎就可以确定 sizeof(HoldsAnInt) == sizeof(int),这就是所说的 emty base optimization。如果你比较在意空间,这种 private 继承方式可能就是一种更好的选择。 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值