当只能在运行时将基类向下转换为子类时,消除了c++虚函数

Eliminating C++ virtual functions when you can only downcast the base class to a sub class at runtime

本文关键字:子类 函数 c++ 转换 运行时 基类      更新时间:2023-10-16

我试图在c++中删除一些紧循环中的虚拟调用…使用CRTP或一般的模板很容易,但显然这需要编译器在编译时知道类型。

在我的程序中,有各种各样的优化实现,比如BitSet,引用BitSet的代码通过基类的引用/指针来实现。

现在我碰巧知道(因为我的对象图是由已识别的类型组成的)一个特定的BitSet引用是哪个子类,所以我可以写一个平凡的"解码器"函数,它有一个代码路径,其中每个BitSet类型都被转换为真正的子类。

然后我可以在该类型上调用非虚函数,这对于一次操作来说是很好的。

我的问题是(我最后一次使用c++是大约10年前,所以我赶上了),我有不同的算法输入/输出,使许多调用bitset的虚函数,所以我想使这些算法的模板版本,由实际的bitset子类模板化。(另一种方法是为每个算法复制"decoder"函数)

这需要我传递算法,无论是模板函数还是类,到"解码器"函数,这就是我遇到麻烦的地方。当我调用解码器时,我不知道BitSet的类型参数是什么,编译器会抱怨-如果我可以以某种方式不指定它,编译器应该拥有生成和调用算法的特定实现所需的所有信息,在每个地方,我在"decoder"函数

中提供了具体类型。

以下是我要采取的步骤:

    第一件事当然是设置基准和分析器。如果不知道你改进了多少,就不可能判断一些改变是好是坏。然后,我会考虑从bitset类中删除所有虚拟的东西,所以你有不同的具体实现,只有相同的接口,但不是派生自基类。这使得在虚函数查找中不可能浪费时间。
  • 作为替代,你可以提供各种装饰器,你可以包装在具体的bitset类,使它们在运行时多态。这允许您仍然使用需要多态bitset类型的现有代码。
  • 最后,在基准测试和分析器的帮助下,您可以将一些仍然采用多态bitset的函数转换为函数模板,这样您就可以将具体的bitset类型作为参数传递。这样做,您可以避免这些特定函数调用的虚函数调用的运行时开销。

我怀疑你基本上是在尝试不可能的事情:如果参数的实际类型在COMPILE TIME不知道,你必须在任何情况下检查类型并调用不同的函数(无论它有不同的名称或相同的名称与不同的模板参数不会改变处理器必须做的事情)。事实上,检查隐式地(通过v-table)或显式地(通过case或if-else if…或者通过函数指针数组:-O)不会改变处理器必须执行的操作的数量。您正在删除v表并将它们重新发明到您自己的代码中。很难改变性能

您的解码器可以有一个模板模板参数。也就是说,你的解码器是在一个算法类上模板化的,这个算法类是在BitSet上模板化的。然后,解码器为特定类型的BitSet创建一个算法类的实例并执行它:

template<template<typename> typename Algorithm>
void decoder(BitSet& bitset) {
  switch(bitset.type()) {
    case 1:
      Algorithm<VectorBitSet>()(static_cast<VectorBitSet&>(bitset));
      break;
    case 2:
      Algorithm<ArrayBitSet>()(static_cast<ArrayBitSet&>(bitset));
      break;
    default:
      throw std::logic_error("Unknown BitSet type");
  }
}
template<typename T>
struct PrintAlgorithm {
  void operator()(const T& bitset) {
    std::cout << "Type: " << bitset.type() << "n";
    std::cout << "Size: " << bitset.size() << "n";
    for(int i = 0; i != bitset.size(); ++i) {
      std::cout << bitset.at(i) << "n";  // No virtual function call. 
    } 
    std::cout << "n";
  }
};

现场演示。