我想知道什么是“虚拟基类”以及它的含义。

让我举个例子:

class Foo
{
public:
    void DoSomething() { /* ... */ }
};

class Bar : public virtual Foo
{
public:
    void DoSpecific() { /* ... */ }
};

当前回答

常规的继承

对于典型的3级非菱形非虚拟继承继承,当实例化一个新的最派生对象时,将调用new,并由编译器从类类型解析堆上对象所需的大小并传递给new。

New有一个特点:

_GLIBCXX_WEAK_DEFINITION void *
operator new (std::size_t sz) _GLIBCXX_THROW (std::bad_alloc)

并调用malloc,返回void指针

This 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 call Type::function() and pass the object pointer to it, cast to Type.

实际上,当我说指针指向它的虚表时,它实际上总是向虚表偏移16。

vtable for Base:
        .quad   0
        .quad   typeinfo for Base
        .quad   Base::CommonFunction()
        .quad   Base::VirtualFunction()

pointer is typically to the first function i.e. 

        mov     edx, OFFSET FLAT:vtable for Base+16

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是另一个编译器保护,它表示该函数正在重写某些内容,如果没有,则抛出编译器错误。

= 0表示这是一个抽象函数

Final防止在派生较多的类中再次实现虚函数,并确保派生最多的类的虚表包含该类的最终函数。

= default使编译器在文档中显式地使用默认实现

= delete如果尝试调用此函数,则给出编译器错误

If 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.

使用带有引用操作符而不是通过指针(解引用操作符)的对象打破了多态性,将虚拟方法视为常规方法。这是因为由于切片,不能对非指针类型进行多态强制转换。

虚拟继承

考虑

class Base
  {
      int a = 1;
      int b = 2;
  public:
      void virtual CommonFunction(){} ; //define empty method body
      void virtual VirtualFunction(){} ;
  };


class DerivedClass1: virtual public Base
  {
      int c = 3;
  public:
    void virtual DerivedCommonFunction(){} ;
     void virtual VirtualFunction(){} ;
  };
  
  class DerivedClass2 : virtual public Base
 {
     int d = 4;
 public:
     //void virtual DerivedCommonFunction(){} ;    
     void virtual VirtualFunction(){} ;
     void virtual DerivedCommonFunction2(){} ;
 };

class DerivedDerivedClass :  public DerivedClass1, public DerivedClass2
 {
   int e = 5;
 public:
     void virtual DerivedDerivedCommonFunction(){} ;
     void virtual VirtualFunction(){} ;
 };
 
 int main () {
   DerivedDerivedClass* d = new DerivedDerivedClass;
   d->VirtualFunction();
   d->DerivedCommonFunction();
   d->DerivedCommonFunction2();
   d->DerivedDerivedCommonFunction();
   ((DerivedClass2*)d)->DerivedCommonFunction2();
   ((Base*)d)->VirtualFunction();
 }

在不实际继承bass类的情况下,你将得到一个如下所示的对象:

而不是这样:

也就是说,会有2个基本对象。

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 constructor DerivedDerivedClass::DerivedDerivedClass(), which calls Base::Base() first, which writes its vtable in the base's dedicated subobject, it then DerivedDerivedClass::DerivedDerivedClass() calls DerivedClass1::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 calls DerivedClass1::DerivedClass1() to do the same, and finally DerivedDerivedClass::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() calling DerivedClass1::DerivedClass1() and that calling Base::Base() (which overwrites the virtual pointer), returning, offsetting the address to the next subobject, calling DerivedClass2::DerivedClass2() and then that also calling Base::Base(), overwriting that virtual pointer, returning and then DerivedDerivedClass 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).

以下代码都是在调试模式-O0下编译的,因此会有冗余的程序集

main:
.LFB8:
        push    rbp
        mov     rbp, rsp
        push    rbx
        sub     rsp, 24
        mov     edi, 48 //pass size to new
        call    operator new(unsigned long) //call new
        mov     rbx, rax  //move the address of the allocation to rbx
        mov     rdi, rbx  //move it to rdi i.e. pass to the call
        call    DerivedDerivedClass::DerivedDerivedClass() [complete object constructor] //construct on this address
        mov     QWORD PTR [rbp-24], rbx  //store the address of the object on the stack as the d pointer variable on -O0, will be optimised off on -Ofast if the address of the pointer itself isn't taken in the code, because this address does not need to be on the stack, it can just be passed in a register to the subsequent methods

顺便说一句,如果代码是DerivedDerivedClass d = DerivedDerivedClass(),主函数看起来会像这样:

main:
        push    rbp
        mov     rbp, rsp
        sub     rsp, 48 // make room for and zero 48 bytes on the stack for the 48 byte object, no extra padding required as the frame is 64 bytes with `rbp` and return address of the function it calls (no stack params are passed to any function it calls), hence rsp will be aligned by 16 assuming it was aligned at the start of this frame
        mov     QWORD PTR [rbp-48], 0
        mov     QWORD PTR [rbp-40], 0
        mov     QWORD PTR [rbp-32], 0
        mov     QWORD PTR [rbp-24], 0
        mov     QWORD PTR [rbp-16], 0
        mov     QWORD PTR [rbp-8], 0
        lea     rax, [rbp-48] // load the address of the cleared 48 bytes
        mov     rdi, rax // pass the address as a pointer to the 48 bytes cleared as the first parameter to the constructor
        call    DerivedDerivedClass::DerivedDerivedClass() [complete object constructor]
        //address is not stored on the stack because the object is used directly -- there is no pointer variable -- d refers to the object on the stack as opposed to being a pointer

回到最初的例子,DerivedDerivedClass构造函数:

DerivedDerivedClass::DerivedDerivedClass() [complete object constructor]:
.LFB20:
        push    rbp
        mov     rbp, rsp
        sub     rsp, 16
        mov     QWORD PTR [rbp-8], rdi
.LBB5:
        mov     rax, QWORD PTR [rbp-8] // object address now in rax 
        add     rax, 32 //increment address by 32
        mov     rdi, rax // move object address+32 to rdi i.e. pass to call
        call    Base::Base() [base object constructor]
        mov     rax, QWORD PTR [rbp-8] //move object address to rax
        mov     edx, OFFSET FLAT:VTT for DerivedDerivedClass+8 //move address of VTT+8 to edx
        mov     rsi, rdx //pass VTT+8 address as 2nd parameter 
        mov     rdi, rax //object address as first (DerivedClass1 subobject)
        call    DerivedClass1::DerivedClass1() [base object constructor]
        mov     rax, QWORD PTR [rbp-8] //move object address to rax
        add     rax, 16  //increment object address by 16
        mov     edx, OFFSET FLAT:VTT for DerivedDerivedClass+24  //store address of VTT+24 in edx
        mov     rsi, rdx //pass address of VTT+24 as second parameter
        mov     rdi, rax //address of DerivedClass2 subobject as first
        call    DerivedClass2::DerivedClass2() [base object constructor]
        mov     edx, OFFSET FLAT:vtable for DerivedDerivedClass+24 //move this to edx
        mov     rax, QWORD PTR [rbp-8] // object address now in rax
        mov     QWORD PTR [rax], rdx. //store address of vtable for DerivedDerivedClass+24 at the start of the object
        mov     rax, QWORD PTR [rbp-8] // object address now in rax
        add     rax, 32  // increment object address by 32
        mov     edx, OFFSET FLAT:vtable for DerivedDerivedClass+120 //move this to edx
        mov     QWORD PTR [rax], rdx  //store vtable for DerivedDerivedClass+120 at object+32 (Base) 
        mov     edx, OFFSET FLAT:vtable for DerivedDerivedClass+72 //store this in edx
        mov     rax, QWORD PTR [rbp-8] //move object address to rax
        mov     QWORD PTR [rax+16], rdx //store vtable for DerivedDerivedClass+72 at object+16 (DerivedClass2)
        mov     rax, QWORD PTR [rbp-8]
        mov     DWORD PTR [rax+28], 5 // stores e = 5 in the object
.LBE5:
        nop
        leave
        ret

DerivedDerivedClass构造函数使用指向偏移量为32的对象的指针调用Base::Base()。Base在它接收到的地址处存储一个指向它的虚表的指针,并在它之后存储它的成员。

Base::Base() [base object constructor]:
.LFB11:
        push    rbp
        mov     rbp, rsp
        mov     QWORD PTR [rbp-8], rdi //stores address of object on stack (-O0)
.LBB2:
        mov     edx, OFFSET FLAT:vtable for Base+16  //puts vtable for Base+16 in edx
        mov     rax, QWORD PTR [rbp-8] //copies address of object from stack to rax
        mov     QWORD PTR [rax], rdx  //stores it address of object
        mov     rax, QWORD PTR [rbp-8] //copies address of object on stack to rax again
        mov     DWORD PTR [rax+8], 1 //stores a = 1 in the object
        mov     rax, QWORD PTR [rbp-8] //junk from -O0
        mov     DWORD PTR [rax+12], 2  //stores b = 2 in the object
.LBE2:
        nop
        pop     rbp
        ret

DerivedDerivedClass::DerivedDerivedClass()然后用指向偏移量为0的对象的指针调用DerivedClass1::DerivedClass1(),并为DerivedDerivedClass+8传递VTT的地址

DerivedClass1::DerivedClass1() [base object constructor]:
.LFB14:
        push    rbp
        mov     rbp, rsp
        mov     QWORD PTR [rbp-8], rdi //address of object
        mov     QWORD PTR [rbp-16], rsi  //address of VTT+8
.LBB3:
        mov     rax, QWORD PTR [rbp-16]  //address of VTT+8 now in rax
        mov     rdx, QWORD PTR [rax]     //address of DerivedClass1-in-DerivedDerivedClass+24 now in rdx
        mov     rax, QWORD PTR [rbp-8]   //address of object now in rax
        mov     QWORD PTR [rax], rdx     //store address of DerivedClass1-in-.. in the object
        mov     rax, QWORD PTR [rbp-8]  // address of object now in rax
        mov     rax, QWORD PTR [rax]    //address of DerivedClass1-in.. now implicitly in rax
        sub     rax, 24                 //address of DerivedClass1-in-DerivedDerivedClass+0 now in rax
        mov     rax, QWORD PTR [rax]    //value of 32 now in rax
        mov     rdx, rax                // now in rdx
        mov     rax, QWORD PTR [rbp-8]  //address of object now in rax
        add     rdx, rax                //address of object+32 now in rdx
        mov     rax, QWORD PTR [rbp-16]  //address of VTT+8 now in rax
        mov     rax, QWORD PTR [rax+8]   //derference VTT+8+8; address of DerivedClass1-in-DerivedDerivedClass+72 (Base::CommonFunction()) now in rax
        mov     QWORD PTR [rdx], rax     //store at address object+32 (offset to Base)
        mov     rax, QWORD PTR [rbp-8]  //store address of object in rax, return
        mov     DWORD PTR [rax+8], 3    //store its attribute c = 3 in the object
.LBE3:
        nop
        pop     rbp
        ret
VTT for DerivedDerivedClass:
        .quad   vtable for DerivedDerivedClass+24
        .quad   construction vtable for DerivedClass1-in-DerivedDerivedClass+24 //(DerivedClass1 uses this to write its vtable pointer)
        .quad   construction vtable for DerivedClass1-in-DerivedDerivedClass+72 //(DerivedClass1 uses this to overwrite the base vtable pointer)
        .quad   construction vtable for DerivedClass2-in-DerivedDerivedClass+24
        .quad   construction vtable for DerivedClass2-in-DerivedDerivedClass+72
        .quad   vtable for DerivedDerivedClass+120 // DerivedDerivedClass supposed to use this to overwrite Bases's vtable pointer
        .quad   vtable for DerivedDerivedClass+72 // DerivedDerivedClass supposed to use this to overwrite DerivedClass2's vtable pointer
//although DerivedDerivedClass uses vtable for DerivedDerivedClass+72 and DerivedDerivedClass+120 directly to overwrite them instead of going through the VTT

construction vtable for DerivedClass1-in-DerivedDerivedClass:
        .quad   32
        .quad   0
        .quad   typeinfo for DerivedClass1
        .quad   DerivedClass1::DerivedCommonFunction()
        .quad   DerivedClass1::VirtualFunction()
        .quad   -32
        .quad   0
        .quad   -32
        .quad   typeinfo for DerivedClass1
        .quad   Base::CommonFunction()
        .quad   virtual thunk to DerivedClass1::VirtualFunction()
construction vtable for DerivedClass2-in-DerivedDerivedClass:
        .quad   16
        .quad   0
        .quad   typeinfo for DerivedClass2
        .quad   DerivedClass2::VirtualFunction()
        .quad   DerivedClass2::DerivedCommonFunction2()
        .quad   -16
        .quad   0
        .quad   -16
        .quad   typeinfo for DerivedClass2
        .quad   Base::CommonFunction()
        .quad   virtual thunk to DerivedClass2::VirtualFunction()
vtable for DerivedDerivedClass:
        .quad   32
        .quad   0
        .quad   typeinfo for DerivedDerivedClass
        .quad   DerivedClass1::DerivedCommonFunction()
        .quad   DerivedDerivedClass::VirtualFunction()
        .quad   DerivedDerivedClass::DerivedDerivedCommonFunction()
        .quad   16
        .quad   -16
        .quad   typeinfo for DerivedDerivedClass
        .quad   non-virtual thunk to DerivedDerivedClass::VirtualFunction()
        .quad   DerivedClass2::DerivedCommonFunction2()
        .quad   -32
        .quad   0
        .quad   -32
        .quad   typeinfo for DerivedDerivedClass
        .quad   Base::CommonFunction()
        .quad   virtual thunk to DerivedDerivedClass::VirtualFunction()

virtual thunk to DerivedClass1::VirtualFunction():
        mov     r10, QWORD PTR [rdi]
        add     rdi, QWORD PTR [r10-32]
        jmp     .LTHUNK0
virtual thunk to DerivedClass2::VirtualFunction():
        mov     r10, QWORD PTR [rdi]
        add     rdi, QWORD PTR [r10-32]
        jmp     .LTHUNK1
virtual thunk to DerivedDerivedClass::VirtualFunction():
        mov     r10, QWORD PTR [rdi]
        add     rdi, QWORD PTR [r10-32]
        jmp     .LTHUNK2
non-virtual thunk to DerivedDerivedClass::VirtualFunction():
        sub     rdi, 16
        jmp     .LTHUNK3

        .set    .LTHUNK0,DerivedClass1::VirtualFunction()
        .set    .LTHUNK1,DerivedClass2::VirtualFunction()
        .set    .LTHUNK2,DerivedDerivedClass::VirtualFunction()
        .set    .LTHUNK3,DerivedDerivedClass::VirtualFunction()


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 type DerivedDerivedClass). The DerivedDerivedClass 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()然后将对象的地址+16和DerivedDerivedClass+24的VTT地址传递给DerivedClass2::DerivedClass2(),其程序集与DerivedClass1::DerivedClass1()相同,除了行mov DWORD PTR [rax+8],在d = 4时显然是4而不是3。

在此之后,它将对象中的所有3个虚表指针替换为DerivedDerivedClass的虚表中指向该类表示的偏移量的指针。

在main中调用d->VirtualFunction():

        mov     rax, QWORD PTR [rbp-24] //store pointer to object (and hence vtable pointer) in rax
        mov     rax, QWORD PTR [rax] //dereference this pointer to vtable pointer and store virtual table pointer in rax
        add     rax, 8 // add 8 to the pointer to get the 2nd function pointer in the table
        mov     rdx, QWORD PTR [rax] //dereference this pointer to get the address of the method to call
        mov     rax, QWORD PTR [rbp-24] //restore pointer to object in rax (-O0 is inefficient, yes)
        mov     rdi, rax  //pass object to the method
        call    rdx

d - > DerivedCommonFunction();。

        mov     rax, QWORD PTR [rbp-24]
        mov     rdx, QWORD PTR [rbp-24]
        mov     rdx, QWORD PTR [rdx]
        mov     rdx, QWORD PTR [rdx]
        mov     rdi, rax  //pass object to method
        call    rdx  //call the first function in the table

d - > DerivedCommonFunction2();。

        mov     rax, QWORD PTR [rbp-24] //get the object pointer
        lea     rdx, [rax+16]  //get the address of the 2nd subobject in the object
        mov     rax, QWORD PTR [rbp-24] //get the object pointer
        mov     rax, QWORD PTR [rax+16] // get the vtable pointer of the 2nd subobject
        add     rax, 8  //call the 2nd function in this table
        mov     rax, QWORD PTR [rax]  //get the address of the 2nd function
        mov     rdi, rdx  //call it and pass the 2nd subobject to it
        call    rax

d - > DerivedDerivedCommonFunction();。

        mov     rax, QWORD PTR [rbp-24] //get the object pointer
        mov     rax, QWORD PTR [rax] //get the vtable pointer
        add     rax, 16 //get the 3rd function in the first virtual table (which is where virtual functions that that first appear in the most derived class go, because they belong to the full object which uses the virtual table pointer at the start of the object)
        mov     rdx, QWORD PTR [rax] //get the address of the object
        mov     rax, QWORD PTR [rbp-24]
        mov     rdi, rax  //call it and pass the whole object to it
        call    rdx

(DerivedClass2 *); d) - > DerivedCommonFunction2 ():

//it casts the object to its subobject and calls the corresponding method in its virtual table, which will be a non-virtual thunk

        cmp     QWORD PTR [rbp-24], 0
        je      .L14
        mov     rax, QWORD PTR [rbp-24]
        add     rax, 16
        jmp     .L15
.L14:
        mov     eax, 0
.L15:
        cmp     QWORD PTR [rbp-24], 0
        cmp     QWORD PTR [rbp-24], 0
        je      .L18
        mov     rdx, QWORD PTR [rbp-24]
        add     rdx, 16
        jmp     .L19
.L18:
        mov     edx, 0
.L19:
        mov     rdx, QWORD PTR [rdx]
        add     rdx, 8
        mov     rdx, QWORD PTR [rdx]
        mov     rdi, rax
        call    rdx

((Base *) d) - > VirtualFunction ();

//it casts the object to its subobject and calls the corresponding function in its virtual table, which will be a virtual thunk

        cmp     QWORD PTR [rbp-24], 0
        je      .L20
        mov     rax, QWORD PTR [rbp-24]
        mov     rax, QWORD PTR [rax]
        sub     rax, 24
        mov     rax, QWORD PTR [rax]
        mov     rdx, rax
        mov     rax, QWORD PTR [rbp-24]
        add     rax, rdx
        jmp     .L21
.L20:
        mov     eax, 0
.L21:
        cmp     QWORD PTR [rbp-24], 0
        cmp     QWORD PTR [rbp-24], 0
        je      .L24
        mov     rdx, QWORD PTR [rbp-24]
        mov     rdx, QWORD PTR [rdx]
        sub     rdx, 24
        mov     rdx, QWORD PTR [rdx]
        mov     rcx, rdx
        mov     rdx, QWORD PTR [rbp-24]
        add     rdx, rcx
        jmp     .L25
.L24:
        mov     edx, 0
.L25:
        mov     rdx, QWORD PTR [rdx]
        add     rdx, 8
        mov     rdx, QWORD PTR [rdx]
        mov     rdi, rax
        call    rdx

其他回答

这意味着对虚函数的调用将被转发到“正确的”类。

C+ FAQ上升。

简而言之,它通常用于多继承场景,其中形成了“菱形”层次结构。虚继承将打破在底层类中创建的模糊性,当您调用该类中的函数并且函数需要解析到底层类之上的类D1或D2时。有关图表和详细信息,请参阅FAQ项。

它还用于姐妹委托,这是一个强大的功能(尽管不适合心脏薄弱的人)。请看这个FAQ。

参见Effective c++第3版中的第40项(第2版中的43项)。

关于内存布局

作为旁注,可怕的钻石的问题是基类出现了多次。通过常规遗传,你相信你有:

  A
 / \
B   C
 \ /
  D

但是在内存布局中,你有:

A   A
|   |
B   C
 \ /
  D

这解释了为什么当调用D::foo()时,你有一个歧义问题。但真正的问题是当你想要使用a的成员变量时。例如,假设我们有:

class A
{
    public :
       foo() ;
       int m_iValue ;
} ;

当你试图从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接口,你更新了酷数组,这有直接改变m_iValue的副作用…

由于m_iValue的改变是直接完成的(不使用虚拟访问方法),通过C“监听”的观察者将不会被调用,因为实现监听的代码是在C中,而B不知道它…

结论

如果你的层次结构中有一颗钻石,这意味着你有95%的概率在该层次结构中做了错事。

解释使用虚拟基的多重继承需要c++对象模型的知识。最好是在一篇文章中,而不是在评论框中,清楚地解释这个主题。

我发现最好的、易读的解释是这篇文章,它解决了我对这个问题的所有疑问:http://www.phpcompiler.org/articles/virtualinheritance.html

在读完这篇文章之后,你真的不需要再阅读关于这个主题的任何其他内容了(除非你是编译器作者)…

钻石继承可运行的用法示例

这个示例展示了如何在典型场景中使用虚拟基类:解决菱形继承问题。

考虑下面的工作示例:

main.cpp

#include <cassert>

class A {
    public:
        A(){}
        A(int i) : i(i) {}
        int i;
        virtual int f() = 0;
        virtual int g() = 0;
        virtual int h() = 0;
};

class B : public virtual A {
    public:
        B(int j) : j(j) {}
        int j;
        virtual int f() { return this->i + this->j; }
};

class C : public virtual A {
    public:
        C(int k) : k(k) {}
        int k;
        virtual int g() { return this->i + this->k; }
};

class D : public B, public C {
    public:
        D(int i, int j, int k) : A(i), B(j), C(k) {}
        virtual int h() { return this->i + this->j + this->k; }
};

int main() {
    D d = D(1, 2, 4);
    assert(d.f() == 3);
    assert(d.g() == 5);
    assert(d.h() == 7);
}

编译并运行:

g++ -ggdb3 -O0 -std=c++11 -Wall -Wextra -pedantic -o main.out main.cpp
./main.out

如果我们移除virtual into:

class B : public virtual A

我们会得到一堵关于GCC无法解析D成员和通过a继承两次的方法的错误墙:

main.cpp:27:7: warning: virtual base ‘A’ inaccessible in ‘D’ due to ambiguity [-Wextra]
   27 | class D : public B, public C {
      |       ^
main.cpp: In member function ‘virtual int D::h()’:
main.cpp:30:40: error: request for member ‘i’ is ambiguous
   30 |         virtual int h() { return this->i + this->j + this->k; }
      |                                        ^
main.cpp:7:13: note: candidates are: ‘int A::i’
    7 |         int i;
      |             ^
main.cpp:7:13: note:                 ‘int A::i’
main.cpp: In function ‘int main()’:
main.cpp:34:20: error: invalid cast to abstract class type ‘D’
   34 |     D d = D(1, 2, 4);
      |                    ^
main.cpp:27:7: note:   because the following virtual functions are pure within ‘D’:
   27 | class D : public B, public C {
      |       ^
main.cpp:8:21: note:    ‘virtual int A::f()’
    8 |         virtual int f() = 0;
      |                     ^
main.cpp:9:21: note:    ‘virtual int A::g()’
    9 |         virtual int g() = 0;
      |                     ^
main.cpp:34:7: error: cannot declare variable ‘d’ to be of abstract type ‘D’
   34 |     D d = D(1, 2, 4);
      |       ^
In file included from /usr/include/c++/9/cassert:44,
                 from main.cpp:1:
main.cpp:35:14: error: request for member ‘f’ is ambiguous
   35 |     assert(d.f() == 3);
      |              ^
main.cpp:8:21: note: candidates are: ‘virtual int A::f()’
    8 |         virtual int f() = 0;
      |                     ^
main.cpp:17:21: note:                 ‘virtual int B::f()’
   17 |         virtual int f() { return this->i + this->j; }
      |                     ^
In file included from /usr/include/c++/9/cassert:44,
                 from main.cpp:1:
main.cpp:36:14: error: request for member ‘g’ is ambiguous
   36 |     assert(d.g() == 5);
      |              ^
main.cpp:9:21: note: candidates are: ‘virtual int A::g()’
    9 |         virtual int g() = 0;
      |                     ^
main.cpp:24:21: note:                 ‘virtual int C::g()’
   24 |         virtual int g() { return this->i + this->k; }
      |                     ^
main.cpp:9:21: note:                 ‘virtual int A::g()’
    9 |         virtual int g() = 0;
      |                     ^
./main.out

在GCC 9.3.0, Ubuntu 20.04上测试。

虚拟类与虚拟继承不同。虚拟类你不能实例化,虚拟继承完全是另一回事。

维基百科描述得比我好。http://en.wikipedia.org/wiki/Virtual_inheritance