1

我已经提到了这个问题(我改变了它的标题)。我知道与virtualness 相关的代码生成是特定于实现的。但是,较早的问题表明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);
}

现在不同的部分在这里:

代码 1(正常继承)

struct Derived : Base {
  Derived () : Base(0) {}
  ~Derived () {}
  int a[100];
};

代码 2(虚拟继承)

struct Derived : virtual Base {
  Derived () : Base(0) {}
  ~Derived () {}
  int a[100];
};

代码3(多重虚拟继承)

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继承没有任何额外的成本,当某些优化是 ON 时?我是否需要执行任何更复杂的测试代码来评估它?请注意,如果不进行优化,这些程序集之间存在差异。

4

2 回答 2

3

而不是性能,我想知道虚拟继承如何处理非虚拟基方法

显然,它将调整this或类指针然后将其传递给原始方法。

如果您以这种方式调整第三个示例,您可能会观察到开销:

  1. 将虚拟方法(非重叠名称)添加到 Base、D1 和 D2。这将导致编译器创建虚拟方法表。
  2. 将不重叠的数据字段/成员变量(不重叠,不同的名称)添加到 Base、D1、D2 和派生。
  3. 向 D2 添加对 D2 和 Base 中的数据字段进行操作的非虚拟方法。
  4. 向 D1 添加对 D1 和 Base 中的数据字段进行操作的非虚拟方法。
  5. 将 non-v\irtual 方法添加到 Derived 中,该方法调用 D2 和 D1 中的上述非虚拟方法,然后对 D2、D1、Base 和 Derived 中的数据字段进行操作。
  6. 调查拆卸。

此外,一旦有一些成员变量,您可能希望在调试器中调查生成的派生类的布局。

当我检查它的组装时,所有 3 个版本之间没有区别

this继承(虚拟或非虚拟)可能会增加一点差异,因为编译器可能会在将类指针从 Derived* 转换为 Base*(或者如果从派生方法调用基本非虚拟方法)或 vfptr时决定调整指向类的指针。这将导致在当前值上增加一些值this或指针传递给函数/方法之前添加一些值。

但是,这很可能会在调用函数调用时完成,并且很可能仅在涉及多重继承时才会发生(因为可能存在多个虚拟方法表)。即,如果您创建的类C继承类A并且B它们都具有虚拟方法,但没有共同的祖先,那么当您调用属于的方法时,AC可能会在反汇编中看到指针调整。但就是这样。这种间接费用的成本将小得离谱。

请注意,这是特定于编译器的问题,我在这里写的所有内容都是基于对微软编译器的观察。即它是“未记录的功能”,因此,如果您担心性能,您应该使用分析器而不是试图猜测性能影响。无论如何,主要优先级应该是代码可读性。

于 2012-02-25T13:12:28.920 回答
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,编译器都知道如何转换它。

您需要一个静态可用类型信息较少的示例来衡量虚拟继承的影响。

于 2012-02-28T04:43:10.430 回答