大家都知道,大C++里面可以私有继承,之后基类的一切,在子类中就成为private的了,不对外开放了。现在流行接口,组合优化继承,所以private继承这玩意,日渐式微,很久以前就很少使用了,嗯,不要说private,就算是大c++,也是江河日下。不过,存在即合理,c++语法里面的任何东西,都有其价值,平时可以用不到,但是关键时刻用一下,确实很方便,当然多数情况下,也可以其他途径来完成,但是,就是没那么舒服。
废话就不说了,直入正题吧。
假设,现在有接口,假设是IUnknown,里面有那三个著名的纯虚函数,QueryInterface, AddRef, Release,好像是这三个哥俩。
然后,有一个类,就叫ClassA,实现了IUnknown接口,其实就是继承IUnknown,也就是说,重写了那三个纯虚函数。此外,ClassA还有一大堆自己的东西,比如public的字段或者成员函数。
现在,有ClassB,想基于ClassA来做一些事情,但是又不想让用户看到ClassA里面那些乱七八糟的玩意,因此,这种情况下,用private似乎很合适。代码如下:
struct IUnknown
{
public:
virtual HRESULT QueryInterface(REFIID riid,void** ppvObject) = 0;
virtual ULONG AddRef() = 0;
virtual ULONG Release() = 0;
};
struct ClassA : IUnknown
{
virtual HRESULT QueryInterface(REFIID riid, void** ppvObject) override { ... }
virtual ULONG AddRef() override { ... }
virtual ULONG Release() override { ... }
...
};
struct ClassB : private ClassA
{
...
};
这里,内存的使用上非常紧凑,可以说,没有多余的地方。但是,这里的private,不仅仅会private ClassA的一切,就连IUnknown也被private,这有时候就不符合要求了,因为这里意图是,private ClassA,但是又想public IUnknown,也就是说,对外界来说,ClassB不是ClassA,虽然其内部基于ClassA实现,但是,又希望ClassB是IUnknown。对此,有几种解决做法,但是都不能让人满意。
方法1、让ClassB再次实现IUnknown接口,如下所示:
struct ClassB : private ClassA, public IUnknown
{
virtual HRESULT QueryInterface(REFIID riid, void** ppvObject) override { ... }
virtual ULONG AddRef() override { ... }
virtual ULONG Release() override { ... }
};
其好处是,ClassB的实例可以无缝用于IUnknown的一切场合,不管是引用或者指针,const非const。但是,代价也是很大的,首先要针对IUnknown的每个虚函数,都要一一手写,再次转发给private的基类,其次,ClassB比ClassA多了一个虚函数表指针,大小就比原来多了一个指针的大小,这就不是零惩罚了,这是最不该。
方法2,还是保持私有继承,再在ClassB中添加几个函数,用以返回IUnknown,代码如下
struct ClassB : private ClassA
{
//也可以using ClassA的三个IUnknown里面的函数
const IUnknown* GetUnknown()const { return this; }
IUnknown* GetUnknown()const { return this; }
};
避开了方法1的不足,但是就不能无缝用于IUnknown下,每次使用必须调用一下GetUnknown(),对于引用的情况下,还必须加多一个星号*,也是挺不方便的。对了,这里就算添加了类型函数重载,也即是operator IUnknown,编译器也拒绝将ClassB无缝转换成IUnknown。
方法3,用包含,不用私有继承。如下:
struct ClassB
{
ClassA mA;
operator const IUnknown&()const { return *this; }
operator IUnknown&() { return *this; }
};
这样子,ClassB的实例可以无缝用于IUnknown引用下的情况。对于指针的话,可以仿造方法2那样子,写两个函数进行调用。貌似综合起来,方法3的整体分数最高。
就个人而言,更偏向于,直接就让ClassB public继承ClassA好了,少了那么多鬼怪,虽然出现很多不必要的函数,其实也没什么不好。