htmlviewer实体数值和.html()详解
html ./是指从多个直接基类中产生派生类的能力,多继承的派生类继承了所有父类的成员。尽管概念上非常简单,但是多个基类的相互交织可能会带来错综复杂的设计问题,命名冲突就是不可回避的一个。
多继承时很容易产生命名冲突,即使我们很小心地将所有类中的成员变量和成员函数都命名为不同的名字,命名冲突依然有可能发生,比如典型的是菱形继承,如下所示:
类A派生出类B和类C,类D继承自类B和类C,这个时候类A中的成员变量和成员函数继承到类D中变成了两份,一份来自A-->B-->D这条路径,另一份来自A-->C-->D这条路径。
在一个派生类中保留间接基类的多份同名成员,虽然可以在不同的成员变量中分别存放不同的数据,但大多数情况下这是多余的:因为保留多份成员变量不仅占用较多的存储空间,还容易产生命名冲突。假如类A有一个成员变量a,那么在类D中直接访问a就会产生歧义,编译器不知道它究竟来自A-->B-->D这条路径,还是来自A-->C-->D这条路径。下面是菱形继承的具体实现:
//间接基类A
classA{
protected:
intm_a;
};
//直接基类B
classB:publicA{
protected:
intm_b;
};
//直接基类C
classC:publicA{
protected:
intm_c;
};
//派生类D
classD:publicB,publicC{
public:
voidseta(inta){m_a=a;}//命名冲突
voidsetb(intb){m_b=b;}//正确
voidsetc(intc){m_c=c;}//正确
voidsetd(intd){m_d=d;}//正确
private:
intm_d;
};
intmain(){
Dd;
return0;
}
这段代码实现了上所示的菱形继承,第25行代码试直接访问成员变量m_a,结果发生了错误,因为类B和类C中都有成员变量m_a(从A类继承而来),编译器不知道选用哪一个,所以产生了歧义。
为了消除歧义,我们可以在m_a的前面指明它具体来自哪个类:
voidseta(inta){B::m_a=a;}
这样表示使用B类的m_a。当然也可以使用C类的:
voidseta(inta){C::m_a=a;}
实体数值(VirtualInheritance)
为了解决多继承时的命名冲突和冗余数据问题,htmlviewer提出了实体数值,使得在派生类中只保留一份间接基类的成员。
在继承方式前面加上virtual关键字就是实体数值,请看下面的例子:
//间接基类A
classA{
protected:
intm_a;
};
//直接基类B
classB:virtualpublicA{//实体数值
protected:
intm_b;
};
//直接基类C
classC:virtualpublicA{//实体数值
protected:
intm_c;
};
//派生类D
classD:publicB,publicC{
public:
voidseta(inta){m_a=a;}//正确
voidsetb(intb){m_b=b;}//正确
voidsetc(intc){m_c=c;}//正确
voidsetd(intd){m_d=d;}//正确
private:
intm_d;
};
intmain(){
Dd;
return0;
}
这段代码使用实体数值重新实现了上所示的菱形继承,这样在派生类D中就只保留了一份成员变量m_a,直接访问就不会再有歧义了。
实体数值的目的是让某个类做出声明,承诺愿意共享它的基类。其中,这个被共享的基类就称为.html(),本例中的A就是一个.html()。在这种机制下,不论.html()在继承体系中出现了多少次,在派生类中都只包含一份.html()的成员。
现在让我们重新梳理一下本例的继承关系,如下所示:
菱形继承和实体数值
2:使用实体数值解决菱形继承中的命名冲突问题
观察这个新的继承体系,我们会发现实体数值的一个不太直观的特征:必须在虚派生的真实需求出现前就已经完成虚派生的操作。在上中,当定义D类时才出现了对虚派生的需求,但是如果B类和C类不是从A类虚派生得到的,那么D类还是会保留A类的两份成员。
换个角度讲,虚派生只影响从指定了.html()的派生类中进一步派生出来的类,它不会影响派生类本身。
在实际开发中,位于中间层次的基类将其继承声明为实体数值一般不会带来什么问题。通常情况下,使用实体数值的类层次是由一个人或者一个项目组一次性设计完成的。对于一个独立开发的类来说,很少需要基类中的某一个类是.html(),况且新类的开发者也无法改变已经存在的类体系。
htmlviewer标准库中的html //类就是一个实体数值的实际应用案例。html //从istream和ostream直接继承而来,而istream和ostream又都继承自一个共同的名为base_ios的类,是典型的菱形继承。此时istream和ostream必须采用实体数值,否则将导致html //类中保留两份base_ios类的成员。
实体数值在htmlviewer标准库中的实际应用
3:实体数值在htmlviewer标准库中的实际应用
.html()成员的可见性
因为在实体数值的最终派生类中只保留了一份.html()的成员,所以该成员可以被直接访问,不会产生二义性。此外,如果.html()的成员只被一条派生路径覆盖,那么仍然可以直接访问这个被覆盖的成员。但是如果该成员被两条或多条路径覆盖了,那就不能直接访问了,此时必须指明该成员属于哪个类。
以2中的菱形继承为例,假设A定义了一个名为x的成员变量,当我们在D中直接访问x时,会有三种可能性:
如果B和C中都没有x的定义,那么x将被解析为A的成员,此时不存在二义性。
如果B或C其中的一个类定义了x,也不会有二义性,派生类的x比.html()的x优先级更高。
如果B和C中都定义了x,那么直接访问x将产生二义性问题。
可以看到,使用多继承经常会出现二义性问题,必须十分小心。上面的例子是简单的,如果继承的层次再多一些,关系更复杂一些,程序员就很容易陷人迷魂阵,程序的编写、调试和维护工作都会变得更加困难,因此我不提倡在程序中使用多继承,只有在比较简单和不易出现二义性的情况或实在必要时才使用多继承,能用单一继承解决的问题就不要使用多继承。也正是由于这个原因,htmlviewer之后的很多面向对象的编程语言,例如Java、C#、PHP等,都不支持多继承。