CRTP Dispatch in C++11

隐身守侯 提交于 2019-12-06 05:42:49

Will line A generate a virtual function call?

Yes. foo_impl() is virtual and Derived overrides it. Even though foo_impl() in Derived is not explicitly tagged as virtual, it is in the base class, and this is enough to make it a virtual function.

Does line B fix the issue I brought up in my previous point (if applicable)?

No. It does not matter if the call is on a pointer or on a reference: the compiler still won't know whether you are invoking the function foo_impl() on an instance of a class that derives from Derived, or on a direct instance of Derived. Thus, the call is performed through a vtable.

To see what I mean:

#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
}

Finally:

Does adding the C++11 final keyword to foo_impl() change how the compiler would act in either (or any other relevant) case?

In theory, yes. The final keyword would make it impossible to override that function in subclasses of Derived. Thus, when performing a function call to foo_impl() through a pointer to Derived, the compiler could resolve the call statically. However, to the best of my knowledge, compilers are not required to do so by the C++ Standard.

CONCLUSION:

In any case, I believe what you actually want to do is not to declare the foo_impl() function at all in the base class. This is normally the case when you use the CRTP. Additionally, you will have to declare class Base<Derived> a friend of Derived if you want it to access Derived's private function foo_impl(). Otherwise, you can make foo_impl() public.

The common idiom for the CRTP does not involve declaring the pure virtual functions in the base. As you mention in one of the comments, that means that the compiler will not enforce the definition of the member in the derived type (other than through use, if there is any use of foo in the base, that requires the presence of foo_impl in the derived type).

While I would stick to the common idiom and not define the pure virtual function in the base, but, if you really feel you need to do it, you can disable dynamic dispatch by adding extra qualification:

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

The use of the extra qualification Derived:: disables dynamic dispatch, and that call will be statically resolved to Derived::foo_impl. Note that this comes will all of the usual caveats: you have a class with a virtual function and paying the cost of the virtual pointer per object, but you cannot override that virtual function in a most derived type, as the use in the CRTP base is blocking dynamic dispatch...

The extra verbiage in lines A and B have absolutely no effect on the generated code. I don't know who recommends this (I've never seen it), but in practice, the only time it might have an effect is if the function isn't virtual. Just write foo_impl(), and be done with it.

There is a means of avoiding the virtual function call if the compiler knows the derived type. I've seen it used for vector-like classes (where there are different implementations, e.g. normal, sparse, etc. of the vector):

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.
    }
};

The idea here is that you normally only work through references to the base class, but if performance becomes an issue, because you're using [] in a tight loop, you can dynamic_cast to the derived class before the loop, and use [] on the derived class.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!