C++11中的CRTP调度

CRTP Dispatch in C++11

本文关键字:调度 CRTP 中的 C++11      更新时间:2023-10-16

假设我有以下代码:

template <class Derived>
class Base {
public:
   virtual void foo_impl() = 0;
   void foo() {
      static_cast<Derived*>(this)->foo_impl(); //A
      (*static_cast<Derived*>(this)).foo_impl(); //B
   }
};
class Derived : public Base<Derived> {
private:
   void foo_impl() {
      bar();
   }
};

几个问题:

线路A会生成一个虚拟函数调用吗?尽管我在互联网上找到的大多数建议都是这样做的,但对我来说,考虑到指向Derived的指针实际上仍然可以指向Derived2类型的对象,其中Derived2:public Derived,我看不出编译器如何进行静态调度。

B行是否解决了我在前一点中提出的问题(如果适用)?考虑到现在调用不再在指针上,因此使用了*,似乎会这样。将避免虚拟函数调用。但是,如果编译器将取消引用的强制转换视为引用类型,它仍然可以生成虚拟函数调用。。。在这种情况下,解决方法是什么?

在foo_impl()中添加C++11 final关键字是否会改变编译器在任何(或任何其他相关)情况下的操作方式?

线路A会生成一个虚拟函数调用吗?

foo_impl()是虚拟的,Derived覆盖它。即使Derived中的foo_impl()没有显式标记为virtual,但它在基类中,这足以使它成为一个虚拟函数。

B行是否解决了我在前一点中提出的问题(如果适用)?

。调用是在指针上还是在引用上并不重要:编译器仍然不知道您是在Derived派生的类的实例上调用函数foo_impl(),还是在Derived的直接实例上调用。因此,该调用是通过vtable执行的。

看看我的意思:

#include <iostream>
using namespace std;
template <class Derived>
class Base {
public:
   virtual void foo_impl() = 0;
   void foo() {
      static_cast<Derived*>(this)->foo_impl();
      (*static_cast<Derived*>(this)).foo_impl();
   }
};
class Derived : public Base<Derived> {
public:
   void foo_impl() {
      cout << "Derived::foo_impl()" << endl;
   }
};
class MoreDerived : public Derived {
public:
   void foo_impl() {
      cout << "MoreDerived::foo_impl()" << endl;
   }
};
int main()
{
    MoreDerived d;
    d.foo(); // Will output "MoreDerived::foo_impl()" twice
}

最后:

foo_impl()中添加C++11 final关键字是否会改变编译器在任何(或任何其他相关)情况下的操作方式?

理论上是的final关键字将使其无法在Derived的子类中覆盖该函数。因此,当通过指向Derived的指针执行对foo_impl()的函数调用时,编译器可以静态地解析该调用。然而,据我所知,C++标准并不要求编译器这样做。

结论

无论如何,我相信您实际想要做的是根本不在基类中声明foo_impl()函数。使用CRTP时通常会出现这种情况。此外,如果希望类Base<Derived>访问Derivedprivate函数foo_impl(),则必须将其声明为Derivedfriend。否则,可以将foo_impl()公开。

CRTP的常用用法不涉及在基中声明纯虚拟函数。正如您在其中一条注释中提到的,这意味着编译器不会在派生类型中强制执行成员的定义(除了通过使用之外,如果在基中使用了foo,则需要在衍生类型中存在foo_impl)。

虽然我会坚持常见的习惯用法,不在基础中定义纯虚拟函数,但是,如果你真的觉得需要这样做,你可以通过添加额外的限定来禁用动态调度:

template <class Derived>
class Base {
public:
   virtual void foo_impl() = 0;
   void foo() {
      static_cast<Derived*>(this)->Derived::foo_impl();
      //                           ^^^^^^^^^
   }
};

使用额外的限定Derived::将禁用动态调度,并且该调用将静态解析为Derived::foo_impl。请注意,这将带来所有常见的注意事项:您有一个带有虚拟函数的类,并为每个对象支付虚拟指针的成本,但您不能在最派生的类型中覆盖该虚拟函数,因为CRTP库中的使用正在阻止动态调度。。。

第A行和第B行中的额外措辞对生成的代码。我不知道是谁推荐这个(我从没见过它),但在实践中,它可能产生影响的唯一时间是如果函数不是虚拟的。只要写foo_impl()

如果编译器知道派生类型。我见过它向量类(在存在不同实现的情况下,例如向量的正常、稀疏等):

template <typename T>
class Base
{
private:
    virtual T& getValue( int index ) = 0;
public:
    T& operator[]( int index ) { return getValue( index ); }
};
template <typename T>
class Derived : public Base<T>
{
private:
    virtual T& getValue( int index )
    {
        return operator[]( index );
    }
public:
    T& operator[]( index )
    {
        //  find element and return it.
    }
};

这里的想法是,你通常只通过引用来工作到基类,但如果性能成为问题,因为如果在紧密循环中使用[],则可以将dynamic_cast循环之前的派生类,并在派生类上使用[]