条款42:明智运用private inheritance
如果classes之间的继承关系是private,编译器通常不会自动将一个derived class object转换为一个base class object。
由private base class继承而来的所有members,在derived class中都会变成private属性,纵使它们在base class中原本是protected或public属性。
Private inheritance意味着“根据某物实现”,其意义只及于软件实现层面。
条款41示范的Stack template,如果你将一个template具现(instantiate)十次,就可能具现出十份template代码,这便是所谓的因template而导致的程序代码膨胀现象。
对于某些种类的classes,可以利用泛型指针来避免,条款41示范代码唯一需要改变的就是把T改为void* ,但是太容易被误用了,因为指针就是指针,没有任何分别,也没有判断类型,于是一个ints栈里面可能误存char是完全有可能的。
这时候便可以用private inheritance,而且该方法较之layering的优势在于,它可以很好的保护GenericStack,不让外界使用。
class GenericStack {
protected:
GenericStack();
~GenericStack();
void push(void *object);
void * pop();
bool empty() const;
private:
... // 同上
};
GenericStack s; // 错误! 构造函数被保护
class IntStack: private GenericStack {
public:
void push(int *intPtr) { GenericStack::push(intPtr); }
int * pop() { return static_cast<int*>(GenericStack::pop()); }
bool empty() const { return GenericStack::empty(); }
};
class CatStack: private GenericStack {
public:
void push(Cat *catPtr) { GenericStack::push(catPtr); }
Cat * pop() { return static_cast<Cat*>(GenericStack::pop()); }
bool empty() const { return GenericStack::empty(); }
};
IntStack is; // 正确
CatStack cs; // 也正确
但是上述方法要以手动方式完成所有的interface classes,可以利用template自动产生他们,如:
template<class T>
class Stack: private GenericStack {
public:
void push(T *objectPtr) { GenericStack::push(objectPtr); }
T * pop() { return static_cast<T*>(GenericStack::pop()); }
bool empty() const { return GenericStack::empty(); }
};
条款43:明智地运用多继承(multiple inheritance,MI)
1、多继承很可能引起模棱两可的情况,有一个重命名的方法:
class AuxLottery: public Lottery {
public:
virtual int lotteryDraw() = 0;
virtual int draw() { return lotteryDraw(); }
};
class AuxGraphicalObject: public GraphicalObject {
public:
virtual int graphicalObjectDraw() = 0;
virtual int draw() { return graphicalObjectDraw(); }
};
class LotterySimulation: public AuxLottery,
public AuxGraphicalObject {
public:
virtual int lotteryDraw();
virtual int graphicalObjectDraw();
...
};
LotterySimulation *pls = new LotterySimulation;
Lottery *pl = pls;
GraphicalObject *pgo = pls;
// 调用LotterySimulation::lotteryDraw
pl->draw();
// 调用LotterySimulation::graphicalObjectDraw
pgo->draw();
2、钻石型继承(虚拟继承)
C++使用虚拟继承(Virtual Inheritance),使得派生类如果继承基类多次,但只有一份基类的拷贝在派生类对象中。
虚拟继承的语法:
class 派生类: virtual 基类1,virtual 基类2,...,virtual 基类n{
...//派生类成员声明
};
多重继承构造执行顺序
首先执行虚基类的构造函数,多个虚基类的构造函数按照被继承的顺序构造;
执行基类的构造函数,多个基类的构造函数按照被继承的顺序构造;
执行成员对象的构造函数,多个成员对象的构造函数按照申明的顺序构造;
执行派生类自己的构造函数;
析构以与构造相反的顺序执行;
为了解决从不同途径继承来的同名的数据成员在内存中有不同的拷贝造成数据不一致问题,将共同基类设置为虚基类。这时从不同的路径继承过来的同名数据成员在内存中就只有一个拷贝,同一个函数名也只有一个映射。这样不仅就解决了二义性问题,也节省了内存,避免了数据不一致的问题。
class 派生类名:virtual 继承方式 基类名
virtual是关键字,声明该基类为派生类的虚基类。
在多继承情况下,虚基类关键字的作用范围和继承方式关键字相同,只对紧跟其后的基类起作用。
声明了虚基类之后,虚基类在进一步派生过程中始终和派生类一起,维护同一个基类子对象的拷贝。
以下面的一个例子为例:
class CA
{
int k; //为了便于说明后面的内存结构特别添加
public:
void f() {cout << "CA::f" << endl;}
};
class CB : public CA
{
};
class CC : public CA
{
};
class CD : public CB, public CC
{
};
void main()
{
CD d;
d.f();
}
当编译上述代码时,我们会收到如下的错误提示:
error C2385: 'CD::f' is ambiguous
即编译器无法确定你在d.f()中要调用的函数f到底是哪一个。
要解决这个问题,有两个方法:
1、重载函数f():此时由于我们明确定义了CD::f,编译器检查到CD::f()调用时就无需再像上面一样去逐级生成CD::f标识了;
此时CD的元素结构如下:
--------
|CB(CA)|
|CC(CA)|
--------
故此时的sizeof(CD) = 8;(CB、CC各有一个元素k)
2、使用虚拟继承:虚拟继承又称作共享继承,这种共享其实也是编译期间实现的,当使用虚拟继承时,上面的程序将变成下面的形式:
class CA
{
int k;
public:
void f() {cout << "CA::f" << endl;}
};
class CB : virtual public CA
{
};
class CC : virtual public CA
{
};
class CD : public CB, public CC
{
};
void main()
{
CD d;
d.f();
}
此时,当编译器确定d.f()调用的具体含义时,将生成如下的CD结构:
----
|CB|
|CC|
|CA|
----
同时,在CB、CC中都分别包含了一个指向CA的vbptr(virtual base table pointer),其中记录的是从CB、CC的元素到CA的元素之间的偏移量。此时,不会生成各子类的函数f标识,除非子类重载了该函数,从而达到“共享”的目的。
也正因此,此时的sizeof(CD) = 12(两个vbptr + sizoef(int));
3、通过不同的继承体系继承
4、如果两个继承于同一个父类的子类之间可以进行代码重用,为什么不把可以重用的方法抽象出来呢?如果不方便放在父类里面,可以新建一个类,该类继承于他们原先的父类,再做子类的父类。
条款44:说出你的意思并了解你所说的每一句话
(1) 共同的base class:意味着共同的特性;
(2) Public inheritance:意味着"是一种(isa)":
class D : public class B:每个D对象也是一个B对象;
(3) Private inheritance:意味着"根据某物实现"
class D : private class B:D对象根据B对象实现,但是B和D没有任何概念上的关系;
(4) Layering:意味着"有一个(has-a)"或"根据某物实现";