【问题标题】:Can an empty virtual table exist?可以存在一个空的虚拟表吗?
【发布时间】:2011-10-21 10:11:35
【问题描述】:
#include <iostream>
using namespace std;

class Z
{
public:
    int a;
    virtual void x () {}
};

class Y : public Z
{
public:
    int a;
};

int main() 
{
    cout << "\nZ: "  << sizeof (Z);
    cout << "\nY: "  << sizeof (Y);
} 

因为 Y 继承了 Z,所以它也会有一个虚拟表。美好的。但是,它没有任何虚函数,那么 Y 的虚表的内容是什么?
会是空的吗?

【问题讨论】:

  • 注意:C++ 标准实际上并没有说虚函数必须用 vtable 来实现。
  • 这个问题有什么特别的原因吗?
  • 如果您的实现遵循 Itanium C++ ABI,dive right in!

标签: c++ virtual vtable


【解决方案1】:

这完全取决于编译器。当我强制实例化 YZ 时,g++ 4.4.5YZ 生成两个不同的虚拟表,它们具有相同的大小。

两个表都指向相同的x(),但指向不同的typeinfo 结构:

;=== Z's virtual table ===
_ZTV1Z:
        .quad   0
        .quad   _ZTI1Z     ; Z's type info
        .quad   _ZN1Z5xEv  ; x()

_ZTI1Z:
        ; Z's type info (omitted for brevity)

;=== Y's virtual table ===
_ZTV1Y:
        .quad   0
        .quad   _ZTI1Y     ; Y's type info
        .quad   _ZN1Z5xEv  ; x()

_ZTI1Y:
        ; Y's type info (omitted for brevity)

【讨论】:

  • 我也打算将其包含在我的答案中,但我认为您做得比我做得更好。
  • 谢谢,顺便说一句,那种类型的信息结构是什么?它包含什么?是指函数的类型还是什么?
【解决方案2】:

在您发布的示例中,GCC 默认会完全优化 vtable。因为它只是一个翻译单元,而且一切都对它可见,这是可能的。

我将您的示例更改为:

#include <iostream>
using namespace std;

class Z
{
public:
    int a;
    virtual void x () const {}
};

class Y : public Z
{
public:
    int a;
};

int main()
{
    Y y;
    const Z& z1=y;
    const Z& z2=Z();
    z1.x(),z2.x();
    cout << "\nZ: "  << sizeof (Z);
    cout << "\nY: "  << sizeof (Y);
}

在这种情况下,输出中会生成一个 vtable:

nm a.out|c++filt|grep -i vtable
08048880 V vtable for Y
08048890 V vtable for Z
0804a040 V vtable for __cxxabiv1::__class_type_info@@CXXABI_1.3
0804a120 V vtable for __cxxabiv1::__si_class_type_info@@CXXABI_1.3

如果我们使用-S 生成程序集,那么我们可以找到构造函数(在我的系统上分别被修改为_ZN1ZC2Ev_ZN1YC2Ev)。这些负责设置 vtables(_ZTV1Z_ZTV1Y):

Z 的构造函数:

_ZN1ZC2Ev:
.LFB970:
        .cfi_startproc
        pushl   %ebp
        .cfi_def_cfa_offset 8
        .cfi_offset 5, -8
        movl    %esp, %ebp
        .cfi_def_cfa_register 5
        movl    8(%ebp), %eax
        movl    $_ZTV1Z+8, (%eax)
        popl    %ebp
        .cfi_def_cfa 4, 4
        .cfi_restore 5
        ret

还有Y

_ZN1YC2Ev:
.LFB972:
        .cfi_startproc
        pushl   %ebp
        .cfi_def_cfa_offset 8
        .cfi_offset 5, -8
        movl    %esp, %ebp
        .cfi_def_cfa_register 5
        subl    $24, %esp
        movl    8(%ebp), %eax
        movl    %eax, (%esp)
        call    _ZN1ZC2Ev
        movl    8(%ebp), %eax
        movl    $_ZTV1Y+8, (%eax)
        leave
        .cfi_restore 5
        .cfi_def_cfa 4, 4
        ret

这里有趣的是,在两个构造函数中放入 vtable 的内容基本相同。

【讨论】:

    【解决方案3】:

    在一般的编译器实现中,Y 的虚拟表将具有与 Z 相同的条目

    【讨论】:

    • 当我使用g++ 编译一个简单的示例时,情况并非如此。查看我的答案以了解我的发现。
    【解决方案4】:

    血淋淋的细节已经添加在其他答案中,但是从非常高的角度来看,您必须认为派生类型Y 确实具有从Z 继承的所有虚函数,它只是不提供覆盖其中任何一个(好吧,对于单个)。

    整个虚函数表的想法是所有从该基础派生的类型将有一个兼容表。当编译器需要找到要调用的虚方法的特定实现时,它知道它可以依赖于存在的表,并且在表的元素中是指向 final-overrider 的指针即使根据您的示例,该 final-overrider 恰好是第一个也是唯一的覆盖器,也适用于该特定对象的该方法。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-01-10
      • 2016-08-22
      • 1970-01-01
      • 2014-12-10
      相关资源
      最近更新 更多