如何鼓励未使用的抽象基类的去虚拟化?

问题描述 投票:0回答:1

我有一个浅层类层次结构(底部的最小可重现代码示例),其中我使用抽象基类来保存一堆相关类之间的大部分通用逻辑,并使用子类重写的虚拟函数(

modifier()
)影响父级中共享方法的行为 (
logic()
)。

代码中不涉及真正的多态性,即我没有在任何地方将子类作为父类指针传递,事实上,除了子类的声明之外,没有在任何地方提到基类,并且为了强制执行这一点,基类甚至在匿名命名空间中(静态链接)。子类被声明为最终类,作为其自身创建并保持这种方式,对于外部代码来说,层次结构可能不存在。我基本上使用继承机制来机械地在各个相关类之间共享大部分代码,而不必自己进行大量的复制粘贴。

鉴于此,我认为这些类将是去虚拟化的主要候选者,但它似乎在 gcc 13.2 或 clang 17.0 等当前编译器中不起作用。编译器可以在非常非常琐碎的情况下省略 vtable,但即使在

-O3
,也只会生成和使用一点间接和 vtable;看到这个神箭:

https://godbolt.org/z/67479h619

有什么方法可以使这段代码更加优化友好并更可靠地摆脱虚函数表吗?标准是否对这种优化有任何说明,或者这一切都取决于编译器和风向吗?或者解决这个问题的唯一好方法是“手动去虚拟化”,即通过简单地将

logic()
手动复制粘贴到每个子类中来完全摆脱虚拟函数?

这里是与上面的 godbolt 相同的示例代码:

// devirtualisation test

namespace ns {

namespace {

struct Base {
    int common() { return 3; }
    virtual int modifier() = 0;
    int logic(int n) {
        return (n * modifier()) % common();
    }
};

} // namespace <anonymous>

struct ChildA final : Base {
    int modifier() { return 2; }
};

struct ChildB final : Base {
    int modifier() { return 3; }
};

struct ChildC final : Base {
    int modifier() { return 4; }
};

} //namespace ns

using namespace ns;

#include <iostream>
using namespace std;

// get* functions to confuse compilers a little

ChildA getA() {
    return ChildA();
}

ChildB getB() {
    return ChildB();
}

int main() {
    cout << getA().logic(2) << "\n";
    cout << getB().logic(2) << "\n";
    cout << ChildC().logic(2) << "\n";
}

显然真实的例子要复杂一些;它是一堆用于迭代遍历树节点的类,其中虚函数负责决定遍历何时停止并返回一个值。因此,我可以通过重写该方法来为中序、前序、仅叶等遍历创建迭代器。如果这看起来像是一个有用的测试,那么这是一个天赐之物(

logic()
modifier()
的对应部分分别是
bstiterbase<T>::walk()
bstiterbase<T>::stop()
):

https://godbolt.org/z/7MjzfbzWb

c++ polymorphism abstract-class compiler-optimization devirtualization
1个回答
0
投票

首先,您的

Base
被定义在匿名命名空间中意味着尝试在另一个翻译单元中定义任何派生类将违反 ODR(因为
Base
会引用不同的新类中的不同类) ,匿名命名空间)。

其次,在纯右值上调用函数将始终被虚拟化。因此,将在类型为

final
类的泛左值上调用函数。您给出的第一个示例是完全去虚拟化的(即,没有间接进入任何 vtable)。 vtable 无法删除,因为它可以在其他翻译单元中使用。

第三,在更大的“真实示例”中,为了对

stop
的调用进行去虚拟化,编译器必须完全内联
step()
walk()
。因此,权衡是执行此操作所需的额外代码大小与仅调用虚函数所损失的速度。

如果你想强制编译器这样做,你可以使用CRTP:

// *Not* in an anonymous namespace
template<typename Self>
struct Base {
    Self& self() { return static_cast<Self&>(*this); }
    int common() { return 3; }
    int logic(int n) {
        return (n * self().modifier()) % common();
    }
};

struct ChildA final : Base<ChildA> {
    int modifier() { return 2; }
};

struct ChildB final : Base<ChildB> {
    int modifier() { return 3; }
};

struct ChildC final : Base<ChildC> {
    int modifier() { return 4; }
};
© www.soinside.com 2019 - 2024. All rights reserved.