문제

Say I have the following code:

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 few questions:

Will line A generate a virtual function call? Although the majority of what I can find on the internet recommends doing things this way, to me I don't see how the compiler can do static dispatch considering that a pointer to Derived could still actually point to an object of type Derived2 where Derived2 : public Derived.

Does line B fix the issue I brought up in my previous point (if applicable)? It seems like it would, considering that now the call is not on a pointer any more and thus using *. would avoid a virtual function call. But if the compiler treats the dereferenced cast as a reference type, it could still generate a virtual function call... in that case, what is the workaround?

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

도움이 되었습니까?

해결책

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.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top