[英]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(){ ... }
};
但是现在我想将DerivedA
和DerivedB
类型的对象放在一个向量中并执行以下代码:
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.