对于public而言,将一个继承类对象转换为一个基类对象再正常不过,但是,对于private来说,编译器不允许隐式的转换,强制转换也有些许的约束。
也就是说下面这样的代码并不可以
#include <iostream>
using namespace std;
class A{
private:
virtual void foo()
{
cout << "A::foo" << endl;
}
};
class B : private A{
public:
void foo() override {
cout << "B::foo" << endl;
}
};
int main()
{
As* pa = static_cast<A *> (new B()); //error
B b;
b.foo();
return 0;
}
为了理解为什么这样,我们可以首先应当理解转换的原理。下面我们将隐式的强制转换或者显示的强制转换称为转换。
转换并不是一个replace的,而是非replace,也就是说,我们在进行转换地时候,编译器会为我们生成一个临时地右值。
比如说下面地代码
double a = 1.31;
(int) a;
int b = a;
熟悉C的读者可能很容会想起来这个机制。
对于自定义类型来说,也同样地遵循这个机制。
好的,现在我们再看看为什么不能将私有继承的对象进行向上的转换。为了进行向上的转换,我们需要调用基类的构造函数来构造一个基类。但同时,私有继承也就意味着对于类外构造函数是不可见的,这里的不可见不是说不知道,而是说不可访问的意思。那这样怎么能构造一个基类的对象呢?
或许我们可以放大以下视野,如果说基类中的构造函数不能对外公开,那么有由子类转换为基类就是一个不可行的行为。这让你想到了什么?没错,就是在C++11之前,我们禁止某个对象的赋值操作,可以将其的所有copying行为的函数(编译器为我们生成的)声明为private,并不予任何实现。又或者是boost库中的nocopyable,他的实现这里就不说了,读者可以很轻松的找到。
让我们将视野放的更广阔一些,让我们从不同权限继承的意义来说。
公有继承意味着我们实现了一个is-a的设计——如果对于公有继承你了继承体系之间不是这样的关系,哦,那赶快重构代码吧。你选错了设计模式。
一旦组织了一个is-a的关系,那也就意味着对于基类的所有行为,也都使用于继承类。所以,这样的向上转换在某些场合下完全没有问题。你并没有理由拒绝这样做。
再让我们看看私有继承意味着什么吧,私有继承意味着根据某物实现出。
这也就意味着,私有继承纯粹只是一种实现技术 (基类所有的所有数据在子类中都是private的),更加具体的来说,私有继承继承了实现方法,而没有继承接口。
好的,既然这样,我们编译器如果为我们做出一个隐式的转换,这样的转换并不是时时刻刻都尽如人意的,所以说编译器拒绝为我们生成这样的代码。你有可能说这样是强词夺理,本来就是因为构造函数的私有性质才不能进行转换的。但你可别忘了,为我们进行隐式转换的工作可是编译器啊,如果读者有兴趣的话,可以看看std::initlized_list是如何实现的——其中就有一个私有的构造函数,让编译器来调用。所以,我的意思是,编译器完全有能力去进行隐式转换,但是那样既没有没有什么必要,又不符合C++语法规则的直觉。
说一些题外话:private继承用的真的是很少很少,但还是有其存在的必要。像Java那样的语言——没有私有继承,虽然也可以做任何你在C++中想做的,但是失去了一些实现方法的弹性。我们喜欢C++不正是喜欢C++的语言的弹性吗?
到了这里,对应当解决的问题和对于问题的一些拓展大致的说了一下,如果想要真正的理解private继承的前世今生,或者说是继承体系的前世今生,应当学习更多的东西。