在 C++ 中,什么是虚拟基类?
我想知道“虚拟基类”是什么以及它的含义。
让我举个例子:
class Foo
{
public:
void DoSomething() { /* ... */ }
};
class Bar : public virtual Foo
{
public:
void DoSpecific() { /* ... */ }
};
I want to know what a "virtual base class" is and what it means.
Let me show an example:
class Foo
{
public:
void DoSomething() { /* ... */ }
};
class Bar : public virtual Foo
{
public:
void DoSpecific() { /* ... */ }
};
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(11)
你有点令人困惑。 我不知道你是否混淆了一些概念。
你的OP中没有虚拟基类。 你只有一个基类。
你做了虚拟继承。 这通常用在多重继承中,以便多个派生类使用基类的成员而不复制它们。
具有纯虚函数的基类不能被实例化。 这需要保罗掌握的语法。 它通常用于派生类必须定义这些函数。
我不想再解释这一点,因为我不完全明白你在问什么。
You're being a little confusing. I dont' know if you're mixing up some concepts.
You don't have a virtual base class in your OP. You just have a base class.
You did virtual inheritance. This is usually used in multiple inheritance so that multiple derived classes use the members of the base class without reproducing them.
A base class with a pure virtual function is not be instantiated. this requires the syntax that Paul gets at. It is typically used so that derived classes must define those functions.
I don't want to explain any more about this because I don't totally get what you're asking.
这意味着对虚函数的调用将被转发到“正确的”类。
C++ FAQ Lite FTW。
简而言之,它经常用于多重继承场景,形成“钻石”层次结构。 当您调用该类中的函数并且该函数需要解析为该底层类之上的类 D1 或 D2 时,虚拟继承将打破底层类中创建的歧义。 有关图表和详细信息,请参阅常见问题解答项。
它也用于姐妹代表团,这是一个强大的功能(尽管不适合胆小的人)。 请参阅此常见问题解答。
另请参阅《Effective C++》第 3 版中的第 40 条(第 2 版中为 43 条)。
It means a call to a virtual function will be forwarded to the "right" class.
C++ FAQ Lite FTW.
In short, it is often used in multiple-inheritance scenarios, where a "diamond" hierarchy is formed. Virtual inheritance will then break the ambiguity created in the bottom class, when you call function in that class and the function needs to be resolved to either class D1 or D2 above that bottom class. See the FAQ item for a diagram and details.
It is also used in sister delegation, a powerful feature (though not for the faint of heart). See this FAQ.
Also see Item 40 in Effective C++ 3rd edition (43 in 2nd edition).
虚拟类与虚拟继承不同。 虚拟类无法实例化,虚拟继承完全是另一回事。
维基百科对它的描述比我更好。 http://en.wikipedia.org/wiki/Virtual_inheritance
Virtual classes are not the same as virtual inheritance. Virtual classes you cannot instantiate, virtual inheritance is something else entirely.
Wikipedia describes it better than I can. http://en.wikipedia.org/wiki/Virtual_inheritance
虚拟继承中使用的虚拟基类是一种在使用多重继承时防止给定类的多个“实例”出现在继承层次结构中的方法。
考虑以下场景:
上面的类层次结构导致“可怕的菱形”,如下所示:
D 的实例将由 B(包括 A)和 C(也包括 A)组成。因此,您有两个“实例” (为了更好的表达)A。
当你遇到这种情况时,你就有可能产生歧义。 当你这样做时会发生什么:
虚拟继承就是为了解决这个问题。 当您在继承类时指定 virtual 时,您就告诉编译器您只需要一个实例。
这意味着层次结构中仅包含 A 的一个“实例”。 因此,
这是一个简短的摘要。 有关更多信息,请阅读此和此。 此处也提供了一个很好的示例。
Virtual base classes, used in virtual inheritance, is a way of preventing multiple "instances" of a given class appearing in an inheritance hierarchy when using multiple inheritance.
Consider the following scenario:
The above class hierarchy results in the "dreaded diamond" which looks like this:
An instance of D will be made up of B, which includes A, and C which also includes A. So you have two "instances" (for want of a better expression) of A.
When you have this scenario, you have the possibility of ambiguity. What happens when you do this:
Virtual inheritance is there to solve this problem. When you specify virtual when inheriting your classes, you're telling the compiler that you only want a single instance.
This means that there is only one "instance" of A included in the hierarchy. Hence
This is a mini summary. For more information, have a read of this and this. A good example is also available here.
关于内存布局
附带说明一下,Dreaded Diamond 的问题是基类出现了多次。 因此,对于常规继承,您相信您拥有:
但在内存布局中,您拥有:
这解释了为什么在调用
D::foo()
时,您会遇到歧义问题。 但当您想要使用A
的数据成员时,真正的问题就出现了。 例如,假设我们有:当您尝试从
D
访问m_iValue
时,编译器会抗议,因为在层次结构中,它会看到两个m_iValue
,没有之一。 如果您修改其中一个,例如B::m_iValue
(即B
的A::m_iValue
父级),C ::m_iValue
不会被修改(即C
的A::m_iValue
父级)。这就是虚拟继承派上用场的地方,有了它,您将回到真正的菱形布局,不仅只有一个
foo()
方法,而且只有一个m_iValue
。可能会出什么问题?
想象一下:
A
有一些基本特征。B
向其中添加了某种很酷的数据数组(例如)C
向其中添加了一些很酷的功能,例如观察者模式(例如,在m_iValue< /代码>)。
D
继承自B
和C
,因此也继承自A
。对于正常继承,从
D
修改m_iValue
是不明确的,必须解决这个问题。 即使是这样,D
内部也有两个m_iValues
,所以你最好记住这一点,并同时更新这两个。使用虚拟继承,从
D
修改m_iValue
是可以的...但是...假设您有D
。 通过其C
接口,您附加了一个观察者。 而通过它的B
接口,你更新了cool数组,这样就有直接改变m_iValue
的副作用...随着
m_iValue
的改变直接完成(不使用虚拟访问器方法),通过C
“监听”的观察者不会被调用,因为实现监听的代码是在C
中,并且B
不知道这一点...结论
如果您的层次结构中有菱形,则意味着您有 95% 的可能性在所述层次结构中做错了事情。
About the memory layout
As a side note, the problem with the Dreaded Diamond is that the base class is present multiple times. So with regular inheritance, you believe you have:
But in the memory layout, you have:
This explain why when call
D::foo()
, you have an ambiguity problem. But the real problem comes when you want to use a data member ofA
. For example, let's say we have:When you'll try to access
m_iValue
fromD
, the compiler will protest, because in the hierarchy, it'll see twom_iValue
, not one. And if you modify one, say,B::m_iValue
(that is theA::m_iValue
parent ofB
),C::m_iValue
won't be modified (that is theA::m_iValue
parent ofC
).This is where virtual inheritance comes handy, as with it, you'll get back to a true diamond layout, with not only one
foo()
method only, but also one and only onem_iValue
.What could go wrong?
Imagine:
A
has some basic feature.B
adds to it some kind of cool array of data (for example)C
adds to it some cool feature like an observer pattern (for example, onm_iValue
).D
inherits fromB
andC
, and thus fromA
.With normal inheritance, modifying
m_iValue
fromD
is ambiguous and this must be resolved. Even if it is, there are twom_iValues
insideD
, so you'd better remember that and update the two at the same time.With virtual inheritance, modifying
m_iValue
fromD
is ok... But... Let's say that you haveD
. Through itsC
interface, you attached an observer. And through itsB
interface, you update the cool array, which has the side effect of directly changingm_iValue
...As the change of
m_iValue
is done directly (without using a virtual accessor method), the observer "listening" throughC
won't be called, because the code implementing the listening is inC
, andB
doesn't know about it...Conclusion
If you're having a diamond in your hierarchy, it means that you have 95% probability to have done something wrong with said hierarchy.
用虚拟基解释多重继承需要了解 C++ 对象模型。 最好在文章中而不是在评论框中清楚地解释该主题。
我发现解决我对这个主题的所有疑问的最好的、可读的解释是这篇文章: http://www.phpcompiler.org/articles/virtualinheritance.html
您真的不需要阅读有关该主题的任何其他内容(除非您是编译器编写者)读完之后...
Explaining multiple-inheritance with virtual bases requires a knowledge of the C++ object model. And explaining the topic clearly is best done in an article and not in a comment box.
The best, readable explanation I found that solved all my doubts on this subject was this article: http://www.phpcompiler.org/articles/virtualinheritance.html
You really won't need to read anything else on the topic (unless you are a compiler writer) after reading that...
我认为你混淆了两种截然不同的东西。 虚拟继承与抽象类不同。 虚拟继承修改函数调用的行为; 有时它会解析函数调用,否则会产生歧义,有时它会将函数调用处理推迟到非虚拟继承中期望的类之外的类。
I think you are confusing two very different things. Virtual inheritance is not the same thing as an abstract class. Virtual inheritance modifies the behaviour of function calls; sometimes it resolves function calls that otherwise would be ambiguous, sometimes it defers function call handling to a class other than that one would expect in a non-virtual inheritance.
我想补充 OJ 的善意澄清。
虚拟继承并不是没有代价的。 与所有虚拟事物一样,您的性能也会受到影响。 有一种方法可以解决这种性能问题,但可能不太优雅。
您可以向菱形添加另一层,而不是通过虚拟派生来破坏菱形,以获得如下结果:
没有一个类进行虚拟继承,所有类都公开继承。 然后,类 D21 和 D22 将隐藏虚函数 f(),这对于 DD 来说是不明确的,也许可以通过将函数声明为私有来实现。 他们分别定义一个包装函数 f1() 和 f2(),每个调用类本地(私有)f(),从而解决冲突。 如果 DD 类需要 D11::f(),则调用 f1();如果需要 D12::f(),则调用 f2()。 如果您内联定义包装器,您可能会获得大约零的开销。
当然,如果您可以更改 D11 和 D12,那么您可以在这些类中执行相同的操作,但通常情况并非如此。
I'd like to add to OJ's kind clarifications.
Virtual inheritance doesn't come without a price. Like with all things virtual, you get a performance hit. There is a way around this performance hit that is possibly less elegant.
Instead of breaking the diamond by deriving virtually, you can add another layer to the diamond, to get something like this:
None of the classes inherit virtually, all inherit publicly. Classes D21 and D22 will then hide virtual function f() which is ambiguous for DD, perhaps by declaring the function private. They'd each define a wrapper function, f1() and f2() respectively, each calling class-local (private) f(), thus resolving conflicts. Class DD calls f1() if it wants D11::f() and f2() if it wants D12::f(). If you define the wrappers inline you'll probably get about zero overhead.
Of course, if you can change D11 and D12 then you can do the same trick inside these classes, but often that is not the case.
除了已经说过的关于多重继承和虚拟继承之外,Dobb 博士的期刊上还有一篇非常有趣的文章: 多重继承被认为有用
In addition to what has already been said about multiple and virtual inheritance(s), there is a very interesting article on Dr Dobb's Journal: Multiple Inheritance Considered Useful
Diamond 继承 runnable 使用示例
此示例展示了如何在典型场景中使用虚拟基类:解决 Diamond 继承问题。
考虑下面的工作示例:
main.cpp
编译并运行:
如果我们将
virtual
删除到:我们会得到一堵关于 GCC 无法解析通过 A 继承两次的 D 成员和方法的错误:
在 GCC 9.3.0、Ubuntu 20.04 上测试。
Diamond inheritance runnable usage example
This example shows how to use a virtual base class in the typical scenario: to solve diamond inheritance problems.
Consider the following working example:
main.cpp
Compile and run:
If we remove the
virtual
into:we would get a wall of errors about GCC being unable to resolve D members and methods that were inherited twice via A:
Tested on GCC 9.3.0, Ubuntu 20.04.
常规继承
对于典型的 3 级非钻石非虚拟继承继承,当您实例化一个新的最派生对象时,将调用
new
并从堆中解析该对象所需的大小编译器的类类型并传递给 new。new
有一个签名:并调用
malloc
,返回 void 指针这个地址然后传递给最底层派生对象的构造函数,该构造函数将立即调用中间的构造函数构造函数,然后中间构造函数将立即调用基本构造函数。 然后,基类在对象的开头存储指向其虚拟表的指针,然后在其后面存储其属性。 然后返回到中间构造函数,该构造函数将其虚拟表指针存储在同一位置,然后将其属性存储在基本构造函数存储的属性之后。 然后,它返回到最派生的构造函数,该构造函数在同一位置存储指向其虚拟表的指针,然后将其属性存储在中间构造函数存储的属性之后。
由于虚拟表指针被覆盖,因此虚拟表指针最终始终是最派生的类之一。 虚拟性向最派生的类传播,因此如果某个函数在中间类中是虚拟的,那么它在最派生的类中也将是虚拟的,但在基类中不是虚拟的。 如果您将最派生类的实例多态转换为指向基类的指针,则编译器不会将其解析为对虚拟表的间接调用,而是直接调用该函数
A::function()< /代码>。 如果一个函数对于您将其转换为的类型来说是虚拟的,那么它将解析为对虚拟表的调用,该虚拟表将始终是最派生类的虚拟表。 如果该类型不是虚拟的,那么它将只调用 Type::function() 并将对象指针传递给它,并强制转换为 Type。
实际上,当我说指向其虚拟表的指针时,它实际上始终是虚拟表中的偏移量 16。
如果 virtual 在派生程度较低的类中是 virtual,则在派生程度较高的类中不再需要 virtual,因为它会向派生程度最高的类的方向向下传播。 但它可以用来表明该函数确实是一个虚函数,而不必检查它继承的类的类型定义。 当一个函数被声明为 virtual 时,从那时起,仅使用继承链中的最后一个实现,但在此之前,如果该对象被强制转换为该类中之前的类的类型,则该函数仍然可以非虚拟地使用。定义该方法的继承链。 在该名称和签名的方法的虚拟性开始之前,它可以在链中之前的多个类中进行非虚拟定义,并且它们在引用时将使用自己的方法(并且链中该定义之后的所有类都将使用该方法)定义(如果它们没有自己的定义),这与虚拟相反,虚拟总是使用最终定义)。 当一个方法被声明为虚拟时,它必须在该类或继承链中的更多派生类中实现,以便构建完整的对象才能使用。
override
是另一个编译器防护,它表示此函数正在覆盖某些内容,如果没有覆盖,则抛出编译器错误。= 0
表示这是一个抽象函数final
防止在更派生的类中再次实现虚函数,并确保最远派生类的虚表包含该类的最终函数。= default
在文档中明确指出编译器将使用默认实现= delete
如果尝试调用此函数,则会出现编译器错误如果调用非虚函数,它将解析为正确的方法定义,而无需通过虚拟表。 如果您调用在继承类中具有最终定义的虚拟函数,那么如果您在调用方法时未将对象指针强制转换为该类型,它将使用其虚拟表并自动将子对象传递给它。 如果您在该类型的指针上调用在最派生类中定义的虚拟函数,那么它将使用其虚拟表,该虚拟表将是对象开头的虚拟表。 如果您在继承类型的指针上调用它,并且该函数在该类中也是虚拟的,那么它将使用该子对象的 vtable 指针,在第一个子对象的情况下,该指针将与最派生类相同的指针,它不会包含 thunk,因为对象和子对象的地址相同,因此它就像自动重铸此指针的方法一样简单,但对于第二个子对象,其 vtable 将包含一个非- virtual thunk 将继承类型的对象的指针转换为最底层派生类中的实现所期望的类型,即完整对象,因此偏移子对象指针以指向完整对象,并且在基子对象的情况下,将需要一个虚拟 thunk 将指向基类的指针偏移到完整对象,以便可以通过方法隐藏对象参数类型对其进行重铸。
使用带有引用运算符而不是通过指针(取消引用运算符)的对象会破坏多态性,并将虚拟方法视为常规方法。 这是因为由于切片,非指针类型上的多态转换不会发生。
虚拟继承
考虑
如果没有虚拟继承 bass 类,您将得到一个如下所示的对象:
而不是这样:
即,将有 2 个基础对象。
在上面的虚拟钻石继承情况中,调用
new
后,它将为对象分配的空间的地址传递给最底层的派生构造函数DerivedDerivedClass::DerivedDerivedClass()
,它首先调用Base::Base()
,将其 vtable 写入基础的专用子对象中,然后DerivedDerivedClass::DerivedDerivedClass()
调用DerivedClass1::DerivedClass1 ()
,它将其虚拟表指针写入其子对象,并通过查询传递的 VTT 覆盖该对象末尾的基子对象指针,然后调用DerivedClass1::DerivedClass1() 执行相同的操作,最后
DerivedDerivedClass::DerivedDerivedClass()
使用该继承类的虚拟表指针覆盖所有 3 个指针。 这不是(如上面第一张图所示)DerivedDerivedClass::DerivedDerivedClass()
调用DerivedClass1::DerivedClass1()
和调用Base::Base ()
(覆盖虚拟指针),返回,将地址偏移到下一个子对象,调用DerivedClass2::DerivedClass2()
,然后还调用Base::Base ()
,覆盖该虚拟指针,返回,然后DerivedDerivedClass
构造函数用其虚拟表指针覆盖两个虚拟指针(在本例中,最派生的构造函数的虚拟表包含 2 个子表)共 3)。以下全部是在调试模式 -O0 下编译的,因此会出现冗余汇编
另外,如果代码是
DerivedDerivedClass d = DerivedDerivedClass()
,则main
函数将如下所示:回到最初的示例,
DerivedDerivedClass
构造函数:DerivedDerivedClass
构造函数使用指向对象偏移量的指针调用Base::Base()
32. Base 将一个指向其虚拟表的指针存储在它收到的地址及其后面的成员中。DerivedDerivedClass::DerivedDerivedClass()
然后使用指向对象偏移量 0 的指针调用DerivedClass1::DerivedClass1()
,并传递 DerivedDerivedClass+8 的VTT 地址
每个继承类都有自己的构造虚表,而最底层的派生类
DerivedDerivedClass
有一个虚表,每个类都有一个子表,它使用指向子表的指针来覆盖构造虚表继承类的构造函数为每个子对象存储的指针。 每个需要 thunk 的虚拟方法(虚拟 thunk 将对象指针从基对象偏移到对象的开头,非虚拟 thunk 将对象指针从非基对象的继承类对象偏移到对象的开头DerivedDerivedClass
类型的整个对象)。 DerivedDerivedClass 构造函数还使用虚拟表表 (VTT) 作为它需要使用的所有虚拟表指针的串行列表,并将其传递给每个构造函数(以及构造函数所在的子对象地址) for),它们用它来覆盖它们和基的 vtable 指针。DerivedDerivedClass::DerivedDerivedClass()
然后将对象的地址+16 和DerivedDerivedClass+24
的 VTT 地址传递给DerivedClass2::DerivedClass2() 的程序集与 DerivedClass1::DerivedClass1()
相同,但mov DWORD PTR [rax+8], 3
行显然有 4 而不是 3 d = 4。此后,它将对象中的所有 3 个虚拟表指针替换为指向 DerivedDerivedClass 的 vtable 中该类表示形式的偏移量的指针。
main
中对d->VirtualFunction()
的调用:d->DerivedCommonFunction();
:d->; DerivedCommonFunction2();
:d->DerivedDerivedCommonFunction();
:((DerivedClass2*)d)->DerivedCommonFunction2();
:((Base*)d)->VirtualFunction();
:Regular Inheritance
With typical 3 level non-diamond non-virtual-inheritance inheritance, when you instantiate a new most-derived-object,
new
is called and the size required for the object on the heap is resolved from the class type by the compiler and passed to new.new
has a signature:And makes a call to
malloc
, returning the void pointerThis address is then passed to the constructor of the most derived object, which will immediately call the middle constructor and then the middle constructor will immediately call the base constructor. The base then stores a pointer to its virtual table at the start of the object and then its attributes after it. This then returns to the middle constructor which will store its virtual table pointer at the same location and then its attributes after the attributes that would have been stored by the base constructor. It then returns to the most derived constructor, which stores a pointer to its virtual table at the same location and and then stores its attributes after the attributes that would have been stored by the middle constructor.
Because the virtual table pointer is overwritten, the virtual table pointer ends up always being the one of the most derived class. Virtualness propagates towards the most derived class so if a function is virtual in the middle class, it will be virtual in the most derived class but not the base class. If you polymorphically cast an instance of the most derived class to a pointer to the base class then the compiler will not resolve this to an indirect call to the virtual table and instead will call the function directly
A::function()
. If a function is virtual for the type you have cast it to then it will resolve to a call into the virtual table which will always be that of the most derived class. If it is not virtual for that type then it will just callType::function()
and pass the object pointer to it, cast to Type.Actually when I say pointer to its virtual table, it's actually always an offset of 16 into the virtual table.
virtual
is not required again in more-derived classes if it is virtual in a less-derived class because it propagates downwards in the direction of the most derived class. But it can be used to show that the function is indeed a virtual function, without having to check the classes it inherits's type definitions. When a function is declared virtual, from that point on, only the last implementation in the inheritance chain is used, but before that, it can still be used non-virtually if the object is cast to a type of a class before that in the inheritance chain that defines that method. It can be defined non-virtually in multiple classes before it in the chain before the virtualhood begins for a method of that name and signature, and they will use their own methods when referenced (and all classes after that definition in the chain will use that definition if they do not have their own definition, as opposed to virtual, which always uses the final definition). When a method is declared virtual, it must be implemented in that class or a more derived class in the inheritance chain for the full object that was constructed in order to be used.override
is another compiler guard that says that this function is overriding something and if it isn't then throw a compiler error.= 0
means that this is an abstract functionfinal
prevents a virtual function from being implemented again in a more derived class and will make sure that the virtual table of the most derived class contains the final function of that class.= default
makes it explicit in documentation that the compiler will use the default implementation= delete
give a compiler error if a call to this is attemptedIf you call a non-virtual function, it will resolve to the correct method definition without going through the virtual table. If you call a virtual-function that has its final definition in an inherited class then it will use its virtual table and will pass the subobject to it automatically if you don't cast the object pointer to that type when calling the method. If you call a virtual function defined in the most derived class on a pointer of that type then it will use its virtual table, which will be the one at the start of the object. If you call it on a pointer of an inherited type and the function is also virtual in that class then it will use the vtable pointer of that subobject, which in the case of the first subobject will be the same pointer as the most derived class, which will not contain a thunk as the address of the object and the subobject are the same, and therefore it's just as simple as the method automatically recasting this pointer, but in the case of a 2nd sub object, its vtable will contain a non-virtual thunk to convert the pointer of the object of inherited type to the type the implementation in the most derived class expects, which is the full object, and therefore offsets the subobject pointer to point to the full object, and in the case of base subobject, will require a virtual thunk to offset the pointer to the base to the full object, such that it can be recast by the method hidden object parameter type.
Using the object with a reference operator and not through a pointer (dereference operator) breaks polymorphism and will treat virtual methods as regular methods. This is because polymorphic casting on non-pointer types can't occur due to slicing.
Virtual Inheritance
Consider
Without virtually inheriting the bass class you will get an object that looks like this:
Instead of this:
I.e. there will be 2 base objects.
In the virtual diamond inheritance situation above, after
new
is called, it passes the address of the allocated space for the object to the most derived constructorDerivedDerivedClass::DerivedDerivedClass()
, which callsBase::Base()
first, which writes its vtable in the base's dedicated subobject, it thenDerivedDerivedClass::DerivedDerivedClass()
callsDerivedClass1::DerivedClass1()
, which writes its virtual table pointer to its subobject as well as overwriting the base subobject's pointer at the end of the object by consulting the passed VTT, and then callsDerivedClass1::DerivedClass1()
to do the same, and finallyDerivedDerivedClass::DerivedDerivedClass()
overwrites all 3 pointers with its virtual table pointer for that inherited class. This is instead of (as illustrated in the 1st image above)DerivedDerivedClass::DerivedDerivedClass()
callingDerivedClass1::DerivedClass1()
and that callingBase::Base()
(which overwrites the virtual pointer), returning, offsetting the address to the next subobject, callingDerivedClass2::DerivedClass2()
and then that also callingBase::Base()
, overwriting that virtual pointer, returning and thenDerivedDerivedClass
constructor overwriting both virtual pointers with its virtual table pointer (in this instance, the virtual table of the most derived constructor contains 2 subtables instead of 3).The following is all compiled in debug mode -O0 so there will be redundant assembly
Parenthetically, if the code were
DerivedDerivedClass d = DerivedDerivedClass()
, themain
function would look like this:Moving back to the original example, the
DerivedDerivedClass
constructor:The
DerivedDerivedClass
constructor callsBase::Base()
with a pointer to the object offset 32. Base stores a pointer to its virtual table at the address it receives and its members after it.DerivedDerivedClass::DerivedDerivedClass()
then callsDerivedClass1::DerivedClass1()
with a pointer to the object offset 0 and also passes the address ofVTT for DerivedDerivedClass+8
Each inherited class has its own construction virtual table and the most derived class,
DerivedDerivedClass
, has a virtual table with a subtable for each, and it uses the pointer to the subtable to overwrite construction vtable pointer that the inherited class's constructor stored for each subobject. Each virtual method that needs a thunk (virtual thunk offsets the object pointer from the base to the start of the object and a non-virtual thunk offsets the object pointer from an inherited class's object that isn't the base object to the start of the whole object of the typeDerivedDerivedClass
). TheDerivedDerivedClass
constructor also uses a virtual table table (VTT) as a serial list of all the virtual table pointers that it needs to use and passes it to each constructor (along with the subobject address that the constructor is for), which they use to overwrite their and the base's vtable pointer.DerivedDerivedClass::DerivedDerivedClass()
then passes the address of the object+16 and the address of VTT forDerivedDerivedClass+24
toDerivedClass2::DerivedClass2()
whose assembly is identical toDerivedClass1::DerivedClass1()
except for the linemov DWORD PTR [rax+8], 3
which obviously has a 4 instead of 3 ford = 4
.After this, it replaces all 3 virtual table pointers in the object with pointers to offsets in
DerivedDerivedClass
's vtable to the representation for that class.The call to
d->VirtualFunction()
inmain
:d->DerivedCommonFunction();
:d->DerivedCommonFunction2();
:d->DerivedDerivedCommonFunction();
:((DerivedClass2*)d)->DerivedCommonFunction2();
:((Base*)d)->VirtualFunction();
: