我想知道什么是“ 虚拟基类 ”及其含义。
让我举一个例子:
class Foo
{
public:
void DoSomething() { /* ... */ }
};
class Bar : public virtual Foo
{
public:
void DoSpecific() { /* ... */ }
};
我想知道什么是“ 虚拟基类 ”及其含义。
让我举一个例子:
class Foo
{
public:
void DoSomething() { /* ... */ }
};
class Bar : public virtual Foo
{
public:
void DoSpecific() { /* ... */ }
};
Answers:
虚拟继承中使用的虚拟基类是一种防止使用多重继承时给定类的多个“实例”出现在继承层次结构中的方法。
请考虑以下情形:
class A { public: void Foo() {} };
class B : public A {};
class C : public A {};
class D : public B, public C {};
上面的类层次结构导致“可怕的菱形”看起来像这样:
A
/ \
B C
\ /
D
D的实例将由B(包括A)和C(其中也包括A)组成。因此,您有A的两个“实例”(为了更好地表达)。
当您遇到这种情况时,就有可能产生歧义。当您这样做时会发生什么:
D d;
d.Foo(); // is this B's Foo() or C's Foo() ??
虚拟继承可以解决这个问题。在继承类时指定virtual时,就是在告诉编译器只需要一个实例。
class A { public: void Foo() {} };
class B : public virtual A {};
class C : public virtual A {};
class D : public B, public C {};
这意味着层次结构中仅包含A的一个“实例”。因此
D d;
d.Foo(); // no longer ambiguous
virtual
,则对象布局看起来像菱形;如果不使用,virtual
则对象布局看起来像是包含两个A
s 的树结构
附带说明一下,Dreaded Diamond的问题在于基类存在多次。因此,通过常规继承,您相信自己具有:
A
/ \
B C
\ /
D
但是在内存布局中,您有:
A A
| |
B C
\ /
D
这解释了为什么在通话时D::foo()
,您有歧义性问题。但是,当您想使用的成员变量时,真正的问题就来了A
。例如,假设我们有:
class A
{
public :
foo() ;
int m_iValue ;
} ;
当您尝试从访问m_iValue
时D
,编译器会提出抗议,因为在层次结构中,它将看到两个m_iValue
,而不是一个。而且,如果您修改一个,例如B::m_iValue
(是的A::m_iValue
父项B
),C::m_iValue
则不会被修改(这是的A::m_iValue
父项C
)。
在这里,虚拟继承很方便,与之类似,您将回到真正的菱形布局,不仅使用一种foo()
方法,而且还使用一种方法,并且只有一种方法m_iValue
。
想像:
A
具有一些基本功能。B
向其中添加一些很酷的数据数组(例如)C
向其中添加了一些很酷的功能,例如观察者模式(例如on m_iValue
)。D
从继承B
和C
,并且因此从A
。对于正常的继承,m_iValue
从D
进行修改是不明确的,必须解决。即使是这样,有两个m_iValues
里面D
,所以你最好记住,并在同一时间更新这两个。
使用虚拟继承,可以m_iValue
从D
进行修改...但是...假设您有D
。通过其C
界面,您连接了一个观察者。通过其B
界面,您可以更新很酷的数组,它的副作用是直接更改m_iValue
...
由于更改m_iValue
是直接完成的(不使用虚拟访问器方法),因此C
不会调用观察者“监听” ,因为实现监听的代码在中C
,并且B
对此一无所知。
如果您的层次结构中有钻石,则意味着您有95%的概率对上述层次结构做错了事。
用虚拟基础解释多重继承需要了解C ++对象模型。最好在文章中而不是在评论框中清楚地说明主题。
我发现可以解决我对此问题的所有疑问的最好的可读性解释是这篇文章:http : //www.phpcompiler.org/articles/virtualinheritance.html
阅读完该主题之后,您真的不需要阅读其他任何相关主题(除非您是编译器作者)...
我想补充一下OJ的种类说明。
虚拟继承并非没有代价。就像虚拟的所有事物一样,您会受到性能的影响。解决这种性能问题的方法可能不太优雅。
无需通过虚拟衍生来破坏钻石,您可以在钻石上添加另一层,以获得如下所示的内容:
B
/ \
D11 D12
| |
D21 D22
\ /
DD
没有一个类是虚拟继承的,所有类都是公开继承的。然后,类D21和D22将隐藏对于DD不明确的虚函数f(),也许是通过将函数声明为私有的。他们分别定义了一个包装函数f1()和f2(),每个函数都调用类本地(私有)f(),从而解决了冲突。类DD如果需要D11 :: f()则调用f1(),如果需要D12 :: f()则调用f2()。如果您内联定义包装器,则可能会获得大约零开销。
当然,如果您可以更改D11和D12,则可以在这些类中执行相同的操作,但通常并非如此。
除了已经说过的关于多重继承和虚拟继承的内容外,Dobb博士的期刊上有一篇非常有趣的文章:多重继承被认为是有用的
你有点困惑。我不知道您是否混淆了一些概念。
您的OP中没有虚拟基类。您只有一个基类。
您进行了虚拟继承。通常在多重继承中使用它,以便多个派生类使用基类的成员而不重现它们。
具有纯虚函数的基类不会被实例化。这需要Paul掌握的语法。通常使用它,以便派生类必须定义这些函数。
我不想再对此进行解释,因为我无法完全理解您的要求。
Diamond继承可运行用法示例
本示例说明如何在典型方案中使用虚拟基类:解决菱形继承。
#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);
}
assert(A::aDefault == 0);
从主要功能给我一个编译错误:aDefault is not a member of A
使用gcc 5.4.0。应该怎么办?
虚拟类是不一样的虚拟继承。您无法实例化的虚拟类,虚拟继承完全是另外一回事。
维基百科比我能更好地描述它。http://en.wikipedia.org/wiki/Virtual_inheritance
对于典型的3级非钻石非虚拟继承,当实例化一个新的最派生对象时,将调用new,并且编译器根据类类型解析该对象所需的大小并将其传递给new。
new有签名:
_GLIBCXX_WEAK_DEFINITION void *
operator new (std::size_t sz) _GLIBCXX_THROW (std::bad_alloc)
并调用malloc
,返回void指针
然后将其传递给派生程度最高的对象的构造函数,该对象将立即调用中间构造函数,然后中间构造函数将立即调用基本构造函数。然后,基础在对象的开头存储指向其虚拟表的指针,然后在其之后存储其属性。然后返回到中间构造函数,该构造函数会将其虚拟表指针存储在相同位置,然后将其属性存储在基本构造函数将存储的属性之后。它返回到最派生的构造函数,该构造函数将指针存储在相同位置的虚拟表,然后将其属性存储在中间构造函数应存储的属性之后。
由于虚拟表指针被覆盖,因此虚拟表指针最终始终是派生程度最高的类之一。虚拟性会向最派生的类传播,因此,如果函数在中产类中是虚拟的,那么它将在最派生类中而不是基类中是虚拟的。如果将大多数派生类的实例多态转换为指向基类的指针,则编译器不会将其解析为对虚拟表的间接调用,而是直接调用该函数A::function()
。如果函数是针对您所转换类型的虚拟函数,则它将解析为对虚拟表的调用,该调用始终是最派生类的调用。如果该类型不是虚拟的,则它将调用Type::function()
并传递对象指针,并将其传递给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
如果在派生程度较低的类中是虚拟的,则它在更高派生的类中不再是必需的,因为它会传播。但是它可以用来表明该函数确实是一个虚函数,而不必检查它继承的类型定义的类。
override
是另一个编译器防护,它表示此函数将覆盖某些内容,如果没有覆盖,则会引发编译器错误。
= 0
表示这是一个抽象函数
final
防止在更多派生类中再次实现虚函数,并确保最派生类的虚拟表包含该类的最终函数。
= default
在文档中明确指出编译器将使用默认实现
= delete
如果尝试对此调用给出编译器错误
考虑
class Base
{
int a = 1;
int b = 2;
public:
void virtual CommonFunction(){} ;
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();
}
在不虚拟继承低音类的情况下,您将获得一个看起来像这样的对象:
代替这个:
即将有2个基础对象。
在上面的虚拟菱形继承的情况下,新的被称为后,它会调用最派生的构造函数,并在构造函数中,它调用的所有3派生的构造函数传递偏移到其虚表的表,而不是调用只是打电话DerivedClass1::DerivedClass1()
和DerivedClass2::DerivedClass2()
,然后将这些都呼Base::Base()
以下都是在调试模式-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 d
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
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 object 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
.LBE5:
nop
leave
ret
它Base::Base()
使用指向对象偏移量32的指针进行调用。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()
然后DerivedClass1::DerivedClass1()
使用指向对象偏移量0的指针进行调用,并传递VTT for DerivedDerivedClass+8
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] //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
.quad construction vtable for DerivedClass1-in-DerivedDerivedClass+72
.quad construction vtable for DerivedClass2-in-DerivedDerivedClass+24
.quad construction vtable for DerivedClass2-in-DerivedDerivedClass+72
.quad vtable for DerivedDerivedClass+120
.quad vtable for DerivedDerivedClass+72
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()
DerivedDerivedClass::DerivedDerivedClass()
然后将object + 16的地址和VTT的地址DerivedDerivedClass+24
传递给DerivedClass2::DerivedClass2()
其,其汇编程序与之相同,DerivedClass1::DerivedClass1()
除了mov DWORD PTR [rax+8], 3
明显带有4而不是3的行d = 4
。
之后,它将对象中的所有3个虚拟表指针替换为指向DerivedDerivedClass
该类表示形式的vtable中的偏移量的指针。
d->VirtualFunction();
:
mov rax, QWORD PTR [rbp-24] //store pointer to virtual table in rax
mov rax, QWORD PTR [rax] //dereference and store in rax
add rax, 8 // call the 2nd function in the table
mov rdx, QWORD PTR [rax] //dereference
mov rax, QWORD PTR [rbp-24]
mov rdi, rax
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
call rdx
d->DerivedCommonFunction2();
:
mov rax, QWORD PTR [rbp-24]
lea rdx, [rax+16]
mov rax, QWORD PTR [rbp-24]
mov rax, QWORD PTR [rax+16]
add rax, 8
mov rax, QWORD PTR [rax]
mov rdi, rdx
call rax
d->DerivedDerivedCommonFunction();
:
mov rax, QWORD PTR [rbp-24]
mov rax, QWORD PTR [rax]
add rax, 16
mov rdx, QWORD PTR [rax]
mov rax, QWORD PTR [rbp-24]
mov rdi, rax
call rdx
((DerivedClass2*)d)->DerivedCommonFunction2();
:
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();
:
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