实际开发中应用private继承的场景并不多,STL的MSVC实现、GCC的libstdc++、Clang的libC++中有少数使用了private继承,而我的实际开发中只有一个应用场景,例如libstdc++中的forward_list:
template<typename _Tp, typename _Alloc = allocator<_Tp>>
class forward_list : private _Fwd_list_base<_Tp, _Alloc>
{
例如libstdc++中的bitset:
template<size_t _Nb>
class bitset : private _Base_bitset<_GLIBCXX_BITSET_WORDS(_Nb)>
{
而实际开发中遇到的一个NonCopyable应用场景:
class NonCopyable
{
protected:
NonCopyable()
{
}
NonCopyable(NonCopyable&&) = delete;
NonCopyable& operator=(NonCopyable&&) = delete;
NonCopyable(const NonCopyable&) = delete;
NonCopyable& operator=(const NonCopyable&) = delete;
};
class Test : private NonCopyable
...
先回顾一下private继承,在对上面的应用进一步解释
private 继承的规则可以总结如下:
如果class之间的继承关系是private,那么继承而来的所有成员在derived class中都为private属性,即使它们在base class中是public属性或者是protected属性
如果class之间的继承关系是private,那么编译器不会自动将一个derived class对象转换为base class对象
下面是public继承例子
class Derived : public Base
{
...
}
void TestPrivateInherit(const Base& nc)
{
...
}
{
Derived d;
TestPrivateInherit(d);
}
这便是通常使用的上行转换,基类指针或者引用指向子类
下面是使用private继承的例子
class Derived : private Base
{
...
}
void TestPrivateInherit(const Base& nc)
{
...
}
{
Derived d;
TestPrivateInherit(d);
}
在编译期编译器报出类似如下错误:
不允许对不可访问的基类 "Base" 进行转换
从上面的规则延展出如下特性:
private继承并不能模塑出像public继承一样的is-a关系
public继承能够模塑出is-a的关系,例如老鹰是鸟类,即老鹰public继承鸟类
private继承意味着derived class采用base class内已经存在的某些特性,derived class和base class之间不存在任何观念上的关系
进一步说明是一种包含关系,例如游戏中的怪物有AI行为,并不能说怪物是AI
private继承意味只有实现部分被继承,接口被略去
这里要知道什么是实现继承和接口继承
从上面的规则看出,private继承可以表达一种包含关系,继而有人可能使用这种方式来表达包含关系,既然这样为什么不选择将继承的类作为子类的private成员变量呢, 实际开发中很少会使用private继承,通常的选择是将其作为private成员变量,大多数的时候是这样
如果设计的类选择private继承,而设计的类有可能又被继承,那么子类有可能重写基类的虚函数,即使选择的是private继承,例如下面代码:
class Base
{
public:
virtual void Test()
{
...
}
}
class Derived : private Base
{
public:
void Invoked()
{
Test();
}
}
class FinalDerived : public Derived
{
public:
void Test() override
{
...
}
}
而通常选择private继承并不希望其子类可以重写虚函数,例如上面的例子Invoked函数调用基类的Test()函数,而Test()函数被其他设计者重写了,对于类Derived的设计者并不希望如此,所以 Derived的设计者应该选择将Base作为其自身的private成员,而不是选择private继承
上面提到大多数的时候不会使用private继承,但是还有少数情况,当两个类不能是is-a关系,并且基类没有成员变量,没有virtual函数,也没有virtual base class可以使用private继承
这样的类是个空类,没有任何对象数据需要存储,例如上面的class NonCopyable,本意上要限定继承者的copy操作,NonCopyable没有任何成员变量,也没有virtual函数
如果使用public继承,也就意味着确定了is-a关系,也就意味着继承者是NonCopyable, 这显然在观念上是错误的,对于使用者这样的代码是成立的
class NonCopyable
{
protected:
NonCopyable()
{
}
NonCopyable(NonCopyable&&) = delete;
NonCopyable& operator=(NonCopyable&&) = delete;
NonCopyable(const NonCopyable&) = delete;
NonCopyable& operator=(const NonCopyable&) = delete;
};
class SceneManager : public NonCopyable
class WidgetManager : public NonCopyable
void Test(NonCopyable* base);
{
SceneManager a;
WidgetManager b;
Test(&a);
Test(&b);
}
其实示例代码中的SceneManager和WidgetManager没有任何关系,对于使用者而言这样的使用方式可以说的过去,但是使用public继承那么基类的析构函数应该是virtual, 这里的基类显然没有定义virtual析构函数,这里使用private继承更为合理
如果说可以让这样的类作为另一个类的private成员变量,那么以NonCopyable为例,如果让NonCopyable作为SceneManager的private成员变量,并不能达成限定copy的效果
下面从空间的角度进一步佐证不能让这样的类作为另一个类的private成员变量
上面提到这样的类其实是个空类,C++规定独立(非附属)对象都必须有非零的大小,C++官方要求对于一个空对象要安插一个char,保证非空, 这就导致以包含关系作为另一个类的private成员变量会导致另一个类的大小不止增加1字节, 因为还有对齐规则,例如下面代码:
class Empty{}
class Widget
{
private:
int x_;
Empty e_;
}
会发现sizeof(Widget) > sizeof(int),所以不止增加1字节,其余空间是由对齐规则而增加的
从规定来看,如果以private继承的方式,这样的类作为基类,那么这样的类就是非独立的,例如下面代码:
class Empty{}
class widget : private Empty
{
private:
int x_;
}
以private继承的方式计算其大小,会发现sizeof(Widget) == sizeof(int),这也就是EBO空基类优化,EBO一般只在单一继承(而非多重继承)下才可行,即EBO技术不能实施于拥有多个base的 derived class身上
另外可以在空基类内使用typedef、enum、static成员变量、non-virtual函数
实际开发中很少遇到空类,像上面的NonCopyable非常贴合private继承的应用场景,当没有足够的理由使用private继承时尽量使用复合的形式,即使private继承也能表达出复合的语义,复合的形式更易于理解
从空间的角度来看,以服务器开发为例,相信以现代的硬件配置不至于让开发者去节省几个字节,但是如果从事其他开发并且非常担忧空间浪费,那么EBO技术可能很有帮助