繁体   English   中英

将 crtp 与通用接口类一起使用时,如何避免虚拟调用开销?

[英]How can I avoid virtual call overhead when using crtp with common interface class?

我出于性能原因使用 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类型的对象放在一个向量中并执行以下代码:

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

我的解决方案是创建一个通用接口类, 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 查找,但也来自在模板的情况下丢失inline

当您将不同的类组合到一个仅在运行时已知的公共结构中时(如这里的 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{}));

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM