为什么虚函数表不能包含重复的函数?

6

想象一个项目,在这个项目中有一个如下所示的接口类:

struct Interface
{
    virtual void f()=0;
    virtual void g()=0;
    virtual void h()=0;
};

假设在其他地方有人希望创建一个实现此接口的类,其中fgh都做同样的事情。
struct S : Interface
{
    virtual void f() {}
    virtual void g() {f();}
    virtual void h() {f();}
};

优化S类的有效方法是生成一个vtable,其中所有条目都指向S::f的指针,从而节省对包装函数g和h的调用。

然而,打印vtable的内容显示这种优化并未执行:

S s;
void **vtable = *(void***)(&s);  /* I'm sorry. */
for (int i = 0; i < 3; i++)
    std::cout << vtable[i] << '\n';

0x400940
0x400950
0x400970

使用-O3-Os编译没有效果,切换clang和gcc也没有用。

为什么会错过这个优化机会呢?

目前,我考虑并排除了以下猜测:

  1. vtable打印代码实际上输出的是垃圾数据。
  2. 性能提升被认为是无用的。
  3. ABI禁止它。

8
这种优化会对符合规范的代码产生影响,因为成员函数指针将会比较相等。 - David Schwartz
2
@David Schwartz:已知llvm可以通过函数指针内联代码。我猜它也可以在“g”和“h”中内联“f”的内容,不过这与OP提出的优化不同...我猜这种优化在实践中并不是非常重要,所以没有人在意?感觉“成员函数指针”相等比较问题可以被编译器检测到,或者标准可以修改为允许优化,就像允许复制省略一样。 - Chris Beck
@PBS 不,情况并非如此。Interface指定至少应有三个函数:fgh来完全实现Interface。由于某种原因,它们被设计成三个不同的函数,具有三个不同的名称。现在,您正在创建一个子类,在其中所有这些函数都执行完全相同的操作。您认为这是一个好的设计吗?答案是否定的,除非出现非常不寻常的情况。此外,我认为这样的“优化”几乎没有任何真正的改进,特别是由于内联。 - Mateusz Grzejek
@MateuszGrzejek:这是什么糟糕的设计?一个例子:'GameObject'有成员函数attackcollidetalk_to。然后在'Dynamite'中,vtable条目应该都指向detonate - PBS
1
@PBS 哦,又是一个“GameObject”层次结构。好的,让我们不要离题了。在我看来,这样的优化并不是必要的。这就是我的全部意见。 - Mateusz Grzejek
显示剩余5条评论
1个回答

2
这种优化无效,因为...
// somewhere-in-another-galaxy.hpp
struct X : S {
    virtual void f();
};

// somewhere-in-another-galaxy.cpp
include <iostream>
void X::f() {
    std::cout << "Hi from a galaxy far, far away! ";
}

如果编译器实现了你的优化,这段代码将无法工作。
Interface* object = new X;
object->g();

我的翻译单元编译器不知道你类的内部实现,所以对于g()和h(),它只是在我的类的虚函数表中放入了对应条目的引用,指向你类的VFT。


问题不在于“你的编译器不知道我的类内部实现”,即使g()在头文件中实现,这是一个“其他编译器不需要继续使我的编译器优化有效”的情况(它可以通过每当覆盖f时覆盖g来实现)。 - PBS
1
另外需要注意的是:当将 S 的定义放在匿名命名空间中并开启 -O2 时,gcc确实会合并所有虚函数表项(从而证明了这个答案的可信度)。 - PBS

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接