c++私有继承与组合浅析

简介

继承是面向对象编程的重要属性。有效地使用继承,可以复用现有的设计,加速产品开发进度。

公有继承塑造了is-a关系,如苹果is a水果,所以苹果可以以public方式继承水果,所有被改写的成员函数满足“不要求更多,也不承诺更少”的原则。

关于私有继承,它体现了is-implemented-in-terms-of关系,但这种关系又可以使用组合的方式来实现。

那么,私有继承与组合之间到底是什么关系?什么时候应该使用私有继承?什么时候又应该尽量使用组合以替代晦涩的私有继承?本文作一探讨。

从一个示例剖析

以下示例从一个模板类使用不同方式生成两个子类:

// base
template <class T>
class MyList
{
public:
	bool Insert(const T&, size_t index);
	T Access(size_t index) const;
	size_t Size() const;
	
private:
	T* buf_;
	size_t bufsize_;
};

//继承方式
template <class T>
class MySet1 : private MyList<T>
{
public:
	bool Add(const T&); // call Insert()
	T Get(size_t index) const; // call Access()
	using MyList<T>::Size;
	//...
};

// 组合方式
template <class T>
class MySet2
{
public:
	bool Add(const T&); // call impl_.Insert()
	T Get(size_t index) const; // call impl_.Access()
	size_t Size() const; // call impl_.Size()
	//...
private:
	MyList<T> impl_;
};

说明:

  • MySet1和MySet2的功能完全相同,没有任何实际意义上的差别
  • 私有继承表现is-implemented-in-terms-of(根据某物实作出)意义,使得子类可以使用基类的public/protected成份
  • 组合表现has-a(有一个)意义,也连带有is-implemented-in-terms-of意义,它只能使用其他类的public成份
  • 私有继承是单组合的一个超集,即,所有组成能完成的事情,私有继承也都能完成
  • 私有继承时,子类只能拥有一份基类,如果需要该类的多个实体,只能使用组合

那么,什么时候需要使用私有继承?

  • 当需要改写虚函数时,特别地,对于纯虚基类,只能继承
  • 需要处理protected 成员时
  • 当类之间的生命周期需要特别注意时,可能需要使用继承
  • 需要分享某个共同的虚基类或者改写某个虚基类的构建程序时
  • 基类是空基类时,即基类没有数据成员时,使用继承可以复用空基类的最佳化而获得空间优势

基于以上分析,示例中在构建MySet时没有理由需要使用继承。组合完美地完成了任务,并减少了类之间的耦合。

组合的优点:

  • 允许使用多个其他类的实例
  • 使得其他类作为一个成员使用,带来更多弹性

对MySet2稍作修改,可以得到一个更多弹性的版本:

template <class T, class Impl = MyList<T>>
class MySet3
{
public:
	bool Add(const T&); // call impl_.Insert()
	T Get(size_t index) const; // call impl_.Access()
	size_t Size() const; // call impl_.Size()
	//...
private:
	Impl impl_;
};

有时候,可能需要私有继承与组合的灵活结合,以达到巧妙复用各自优点的效果。

根据如下基类:

class B
{
public:
	virtual int Func1();
protected:
	bool Func2();
private:
	bool Func3(); // call Func1
};

需要改写虚函数Func1,或存取Func2,就需要使用继承。如下:

class D : private B
{
public:
	int Func1();
	//... 
	// maybe call B::Func2()
};

这份代码满足了要求,允许D改写Func1。

但是,它也允许所有D的成员取用Func2,并非所有成员都需要的。但私有继承还是把protected接口全部透露了出来。

很明显,私有继承是必要的,那么我们如何能够只导入真正需要的耦合呢?

增加一点代码,就可以做的更好。

// 私有继承
class DerivedImpl : private B
{
public:
	int Func1();
	//... need call Func2
};

// 组合
class Derived
{
// ... not use Func2
private:
	DerivedImpl impl_;
};

看看,这样就良好地警卫并封装了对B的依赖。Derived只直接依赖B的public接口和DerivedImpl的public接口。

这也遵循了“一个类一个任务”的设计准则。

小结

继承往往被过度运用。在设计过程中,耦合关系要尽量减少。

如果类与类之间的关系可以有多种方式来表达,就使用其中关系最弱的一种。而继承几乎是最强烈的关系。

一般来说,可以参考以下:

  • 如果组合可行,就不用考虑继承
  • 如果私有继承ok,不要使用public继承
  • 如果类之间可以使用多种关系,就使用耦合最弱的一种
  • 避免使用多重继承
参考资料

《Exxceptional C++》

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值