c++ - 将 crtp 与公共(public)接口(interface)类一起使用时,如何避免虚拟调用开销?

标签 c++ c++11 polymorphism crtp

出于性能原因,我使用 CRTP,因此我有以下层次结构:

template <typename Derived>
class Base{
  public:
    double do_sthing(){
      static_cast<Derived*>(this)->do_sthing();
      }
};

class DerivedA : public Base<DerivedA>{
  public:
    inline double do_sthing(){ ... }
 };

class DerivedB : public Base<DerivedB>{
  public:
    inline double do_sthing(){ ... }
  };

但现在我想将 DerivedADerivedB 类型的对象放入 vector 中并执行以下代码:

 for(auto obj : my_vector) {
   obj->do_sthing();
 }

我的解决方案是创建一个通用接口(interface)类,Base将从中继承:

class Interface {
  virtual void do_sthing() = 0;
};

但在这种情况下,我会产生虚拟调用开销,并且我执行了所有 CRTP 来避免它。有没有办法避免这种情况? 我想将 Base 中的方法 do_sthing 声明为 Final。

template <typename Derived>
class Base{
  public:
  double do_sthing() override final{
    static_cast<Derived*>(this)->do_sthing();
  }
};

我会承受上述虚拟通话费用吗? 事实上,如果我使用虚拟方法和普通的旧多态性,我的性能损失不仅来自 vtable 查找,而且还来自模板情况下丢失内联

最佳答案

当您将不同的类组合到一个仅在运行时才知道的公共(public)结构(如此处的 std::vector)时,您无法避免虚拟调用开销。这是一般的 C++ 原则,例如在 std::function、std::any 等中也可以找到。 CRTP 在这方面没有什么不同。

此外,CRTP(也称为静态多态性)主要是一种避免代码重复的工具(没有任何性能开销)。它并不是为了加速虚拟函数调用,因为那是动态多态性的东西。

除此之外,您基本上有两种方法来设置您的类:

一种方法是使用三重继承层次结构,我发现通过不同的函数名称分隔动态和静态继承很有用。示例:

struct Interface
{
     virtual ~Interface() = default;
     virtual double do_sthing_impl() = 0;
     auto do_sthing() { return this->do_sthing_impl(); }
};

template<typename Derived>
struct Base<Derived> : public Interface
{
     virtual double do_sthing_impl() const override { return this->do_sthing(); }
     auto do_sthing() { return static_cast<Derived&>(*this).do_sthing(); }
};

struct DerivedA : public Base<DerivedA>
{
     auto do_sthing() { /* ... */ }
};

然后:

std::vector<std::unique_ptr<Interface> > v;
v.push_back(std::make_unique<DerivedA>());
v.push_back(std::make_unique<DerivedB>());

第二种选择是type_erasure,例如通过以下代码(基本上是发明了一个继承结构,但没有紧密耦合)

template<typename Derived>
struct Base{ /* ... */ };

struct DerivedA : public Base<DerivedA>{ /* ... */ };

struct type_erased
{
     virtual double do_sthing() = 0;
};

template<typename Derived>
struct type_erased_impl : public type_erased
{
     Derived d;
     type_erasure_impl(Derived d) : d(std::move(d)) {}
     virtual double do_sthing() const override { return d.do_sthing(); }
};

然后:

std::vector<std::unique_ptr<type_erased> > v;
v.push_back(std::make_unique<type_erased_impl<DerivedA> >(DerivedA{}));
v.push_back(std::make_unique<type_erased_impl<DerivedB> >(DerivedB{}));

我更喜欢第二种选择,因为它更灵活并且不引入固定的继承结构。相反,您可以实现同一类的多个类型删除版本,根据实际需要,仅公开特定的函数。

关于c++ - 将 crtp 与公共(public)接口(interface)类一起使用时,如何避免虚拟调用开销?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/60975375/

相关文章:

c++ - GetTokenInformation 基础知识

c++ - Boost Graph 通过 vertex_descriptor 访问属性

c++ - 我们能否将规范性引用中未明确引用的内容应用于 C++ 标准?

c# - 用于状态处理的多态枚举

c++ - 按点坐标旋转点 vector

c++ - "This declaration has no storage class or type specifier in C++"

c++ - 使用自定义散列函数插入 unordered_set

c++ - 我可以列表初始化一个只 move 类型的 vector 吗?

java - java中动态多态性的实时示例是什么

c# - 如何在 C# 中以更通用的方式编写这些方法?