在以下情况下,virtual
用于解决菱形问题,使 A 类的子对象与 B 和 C 共享。
例如:
class A { };
class B : virtual public A { };
class C : virtual public A { };
class D : public B, public C { };
这种类型的继承会在编译时还是运行时解决?我的意思是,在函数和类上使用 virtual 的含义有多大不同?使用virtual从基类继承时是否有动态绑定的概念?
在以下情况下,virtual
用于解决菱形问题,使 A 类的子对象与 B 和 C 共享。
例如:
class A { };
class B : virtual public A { };
class C : virtual public A { };
class D : public B, public C { };
这种类型的继承会在编译时还是运行时解决?我的意思是,在函数和类上使用 virtual 的含义有多大不同?使用virtual从基类继承时是否有动态绑定的概念?
正如您所说,继承不必“解决”,所以问题不是很清楚。
重要的区别在于普通继承和虚拟继承。如果您通常继承,即B : A
, C : A
,则该类D : B, C
有两个type 的子类A
,即D::B::A
和D::C::A
。另一方面,如果B
并且虚拟C
地继承自,那么最终的子类组合将被推迟到您定义最终类型为止。也就是说,它们各自都有一个虚拟子类,如果您要实例化其中一个或. 另一方面,如果您进一步从类派生,那么最派生的类将只包含一个type 的子类。A
B : virtual A
C : virtual A
A
B
C
A
也许您可能想考虑与成员函数的类比。如果每个派生类都添加一个与基函数同名的成员函数,那么最终会得到几个不同的函数。另一方面,如果基函数是virtual
,那么您只有一个函数,该函数定义在最派生的类中。每个中间类中仍然有某种函数(假设函数不是纯虚拟的),但只有最终类定义了“活动”定义。
虚拟继承对构造函数有影响,即虚拟基构造函数(即A()
在示例中)由最派生的类直接调用,即D
,而不是B
或C
。如果你愿意,这是因为只有D
“知道”它只包含一个A
-subclass,所以它直接“负责”它。B
并且C
只保留一个虚拟占位符,让位于最终的、最派生的类。
通过任何基指针/引用访问成员函数的行为与预期的一样,并以通常的方式解析(即,通常是动态的),但这与虚拟继承无关。实际的函数查找可能会更复杂一些,因为它可能涉及额外的间接级别,但这不会改变任何基本的东西。
这种类型的继承会在编译时还是运行时解决?
继承定义了类型的形状(内存占用),并且总是在编译时解决。另一方面,对基类型成员的访问将在运行时解决,原因是层次结构中的中间类型无法知道基子对象将在最派生类型中放置在内存中的什么位置。
我的意思是,在函数和类上使用 virtual 的含义有多大不同?
各个级别的类型和功能都不一样,这里就不多说了。唯一的共同点是有一部分工作在编译时无法完全解决。特别是,相似之处在于,使用虚函数的代码依赖于 vptr(虚表指针)来为最派生类型找到合适的虚表(虚表),并且以类似的方式,访问基对象需要使用存储在虚拟派生自基类型的每个子对象中的指针。
您可以在Itanium C++ ABI中阅读更多信息(关于特定实现,所有这些都不是标准的一部分),尤其是在Non-POD Class Types中。
作为一个简短的总结,每当一个类型 D 虚拟地继承一个类型 B 时,D 子对象将包含一个指向 B 子对象的隐藏指针。需要该指针是因为 B 子对象相对于 D 的相对位置可以随着进一步的继承而改变。
我的理解是虚拟类解决了编译时的歧义。例如假设 B 和 C 都有一个 getSize 方法。
如果没有 virtual,对 D.getSize 的调用会尝试使用基类 getSize 方法。由于 B 和 C 都有一个 getSize 方法,编译器无法正确解决歧义,代码将无法编译。
使用 virtual 时,对 D.getSize 的调用使用死去的 getSize 方法,允许代码正确编译。