实用经验 81 谨慎的使用private继承

我们都知道,public继承可视为is-a关系。定义一个继承关系:CStudent: public CPerson。可向一个函数原本类型为CPerson形参传递CStudent对象。代码如下:

class CPerson {......};
class CStudent : public CPerson {.......};
void Eate(const CPerson *pPerson); 		//  任何人都可执行吃动作
void Study(const CStudent  *pStudent); 	//  任何学生都可以执行学习动作
CPerson  damao;  		// damao 是一个人。
CStudent  xiaoming;  	// xiaoming是一个学生

Eate(&damao);      		    // damao是人,所以可以执行吃动作。
Study(&xiaoming);   		// xiaoming是学生,所以可以执行学习动作。

Eate(&xiaoming);    // xiaoming是学生同样也是人,所以可以执行吃动作。
Study(&damao);     // 错误:damao是人,但不是学生,所以不可以执行学习动作。

CStudent隐式转化为了CPerson,这也从一个侧面反映了CStudent is a CPerson的论断,但是如果CStudent和CPerson之间不是public继承关系,而是private继承关系呢?

class CPerson {......};
class CStudent : private  CPerson {.......};
void Eate(const CPerson *pPerson); 			//  任何人都可执行吃动作
void Study(const CStudent  *pStudent);			//  任何学生都可以执行学习动作
CPerson  damao; 		// damao 是一个人。
CStudent xiaoming; 		// xiaoming是一个学生

Eate(&damao); 			// damao是人,所以可以执行吃动作。
Study(&xiaoming); 		// xiaoming是人,所以可以执行吃动作。

Eate(&xiaoming); 		// 错误:xiaoming是学生,不是人,不能执行吃动作。
Study(&damao);  		// 错误:damao是人,但不是学生,所以不可以执行吃学习动作。

通过上面的例子可以看出,显然private继承并不意味着is a。学生不是人了,显然是逻辑上出现了问题,原因为何呢?来看private继承的原理。

private继承原理如下:

(1)采用private继承时,编译器并不会把派生类(CStudent类)转型为基类(CPerson类)。这就是为何Eate(&xiaoming)会失败的原因。

(2)采用private继承时,基类中的所有public、protected成员函数会成为派生类中的private成员。

(3)Private继承是,派生类和基类不是is a关系,而是一种实现关系。

现在,我们明白了上述例子无法运行的原理了。但是private继承意味着什么呢?其实他意味着is -implemented-in-terms-of(是根据……实现的)。也就是说,如果你声明class Drived:private Base。只能说明你对 class Base的某些特性感兴趣,而不是因为在class Drived和class Base的对象之间有什么概念上的关系。

私有继承仅展示的是class Drived依靠class Base而实现这种概念,除此之外别无其他了。private继承意味只有实现部分被继承, 接口部分应略去。如果classes之间的继承关系是private,编译器不会自动将一个derived class对象转换为一个base class对象,而pubic展示的是class Drived是一种class Base。

私有继承的一个经典案例:如果我们需要每隔1ms刷新一下界面或处理一下界面中的数据信息。我们看Effective C++给出的实例代码。

class Timer 
{
public:
    explicit Timer(int tickFrequency);
    virtual void onTick() const; 	// 每隔tick到达是都会回调此函数的。
    ...
};
class Widget: private Timer
{
private:
    virtual void onTick() const; 	// 查看Widget的数据,执行界面刷新和数据处理
    ...
};

通过私有继承,Timer的public onTick 函数在Widget 中变成 private的,将onTick放入公有接口将误导客户认为他们可以调用它,而这违背了使接口应易于正确使用的论述。所以这里必须声明为private。

虽然,私有继承可以解决这类问题。但在这有时候并非必要。你可以通过尽量用复合代替private继承。因为复合可避免virtual函数重定义。我们看复合实现代码:

//复合实现
class Widget
{
private:
	class WidgetTimer : : public Timer
	{
	public:
	virtual void onTick() const;  // 查看Widget的数据,执行界面刷新和数据处理
	};
	WidgetTimer timer;
};

最佳实践

在遇到private继承时,请尽量用复合机制替换之。因为这样可以带来两个好处:

  • 你可能要做出允许Widget有派生类的设计,但是你还可能要禁止派生类重定义 onTick。如果Widget 从Timer继承,那是不可能的,即使继承是private的也不行。但是组合机制可以。
  • 可最小化Widget的编译依赖。

请谨记

  • 私有继承意味着is-implemented-in-terms of(是根据……实现的)。它通常比composition(复合)更低级,但当一个 derived class(派生类)需要访问protected base class members(保护基类成员)或需要重定义 inherited virtual functions(继承来的虚拟函数)时它就是合理的。
  • 与composition(复合)不同,私有继承能使 empty base optimization(空基优化)有效。这对于致力于最小化 object sizes(对象大小)的库开发者来说可能是很重要的。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值