我提到this question(我更改了标题)。我知道与virtual
相关的代码生成是特定于实现的。但是,之前的问题表明,在调用非虚拟基本方法时,存在与virtual
继承相关的额外成本。
我编写了以下测试代码,并用g ++(-O4
)检查了它的汇编:
struct Base {
int t_size;
Base (int i) : t_size(i) {}
virtual ~Base () {}
int size () const { return t_size; };
};
struct D1 : virtual Base {
int a[10];
D1 () : Base(0) {}
~D1 () {}
};
struct D2 : virtual Base {
int a[20];
D2() : Base(0) {}
~D2 () {}
};
...
void foo (Base *p)
{
if(p->size())
return;
p = 0;
}
int main ()
{
Derived d;
foo(&d);
}
现在差异部分在这里:
struct Derived : Base {
Derived () : Base(0) {}
~Derived () {}
int a[100];
};
struct Derived : virtual Base {
Derived () : Base(0) {}
~Derived () {}
int a[100];
};
struct Derived : D1, D2 {
Derived () : Base(0) {}
~Derived () {}
int a[100];
};
当我检查其装配时,所有3个版本之间存在无差异。以下是汇编代码:
.file "virtualInheritFunctionCall.cpp"
.text
.p2align 4,,15
.globl _Z3fooP4Base
.type _Z3fooP4Base, @function
_Z3fooP4Base:
.LFB1:
.cfi_startproc
rep
ret
.cfi_endproc
.LFE1:
.size _Z3fooP4Base, .-_Z3fooP4Base
.section .text.startup,"ax",@progbits
.p2align 4,,15
.globl main
.type main, @function
main:
.LFB2:
.cfi_startproc
xorl %eax, %eax
ret
.cfi_endproc
.LFE2:
.size main, .-main
.ident "GCC: (Ubuntu/Linaro 4.6.1-9ubuntu3) 4.6.1"
.section .note.GNU-stack,"",@progbits
当某些优化开启时,是否意味着virtual
继承没有任何额外费用?我是否需要执行任何更复杂的测试代码来评估此问题?请注意,如果没有优化,这些程序集之间就会有所不同。
答案 0 :(得分:3)
我想知道虚拟继承如何处理非虚拟基本方法
,而不是性能
显然,它会调整this
或类指针,然后将其传递给原始方法。
如果你以这种方式调整第三个例子,你可能会观察到开销:
此外,一旦有一些成员变量,您可能希望调查调试器中生成的Derived类的布局。
当我检查它的装配时,所有3个版本之间没有区别
继承(虚拟或非虚拟)可能会增加一点差异,因为编译器可能会在将其从Derived *转换为Base *时调整指向类的指针(如果从基础非虚方法调用,则可以this
派生方法)或vfptr。这将导致在将this
或指针传递给函数/方法之前向其添加一些值。
但是,这最有可能在调用函数调用时完成,并且只有在涉及多个继承时才会发生(因为可能存在多个虚方法表)。即如果你创建继承类C
和A
的类B
并且它们都有虚拟方法但没有共同的祖先,那么当你从{调用属于A
的方法时{1}}您可能会在反汇编中看到指针调整。但就是这样。这种开销的成本将非常小。
请注意,这是特定于编译器的问题,我在这里写的所有内容都是基于对microsoft编译器的观察。即它是“未记录的功能”,因此,如果您担心性能,则应使用分析器而不是尝试猜测性能影响。无论如何,主要优先级应该是代码可读性。
答案 1 :(得分:2)
首先,看看foo
:
void foo (Base *p)
{
if(p->size())
return;
p = 0;
}
由于Base::size()
非虚拟,因此p->size()
没有虚拟调度开销。
接下来,看看你如何调用foo
:
int main ()
{
Derived d;
foo(&d);
}
这里,您将获取静态已知类型的实例的地址,即,给定Derived
的实例,编译器可以静态地确定如何将其转换为Base *
。因此,无论Derived
如何从Base
继承,编译器都知道如何转换它。
您需要一个静态可用的较少类型信息的示例来衡量虚拟继承的影响。