Instantiation of derived template class objects

288 Views Asked by At

I have class template acting as base class for derived classes. The idea was to take advantage of "static polymorphic" through the CRTP trick.

#include <iostream>
template <typename T>
class BASE
{
  public:
    void write() {static_cast<T*>(this)->write(); }
};

class DER1 : public BASE<DER1>
{
  public:
    void write(){
       std::cout << "Calling write() inside DER1 " << number << std::endl;}   
  private:
    int number = 11;
};

I tried 2 different ways of instantiating derived class object, and, I found that one of the two ways is not correct. But I could not understand why.

int main(void) {

    BASE<DER1> der1_objA ;
    der1_objA.write();

    DER1 der1_objB ;
    der1_objB.write();
    return 0;
}

In fact, I get as output

Calling write() inside DER1 1880535040    [ random number]
Calling write() inside DER1 11            [correct number ]

Can anybody explain me where the issue is? Thank you very much in advance.

2

There are 2 best solutions below

0
On BEST ANSWER

When you define an object of type BASE, it is just a BASE, but inside it you case the this pointer to something it is not (DER1), and proceed to use it through that invalid pointer. That is undefined behavior, and garbage is a normal consequence. The only time when CRTP works is when the dynamic type of the object actually is the template parameter passed to the base class. That is, if BASE thinks it really is a DER1, it really must be a DER1. Casting itself to DER1 when it is only BASE and using that pointer for DER1 operations is undefined behavior, not much different from doing this:

int x = 42;
std::string * sptr = (std::string*)&x; // extremely questionable
sptr->clear(); // undefined behavior

You should consider making the BASE constructors have "protected" access level, to prevent the simple cases of misuse. This way the base constructor can only be called by derived objects, so you can't accidentally instantiate the base in isolation:

template <typename T>
class BASE
{
  protected:
    BASE() = default;
    BASE(BASE const&) = default;

  public:
    void write() {static_cast<T*>(this)->write(); }
};

Then instead of getting garbage, you get:

BASE<DER1> objA ;
objA.write();

error: 'BASE<T>::BASE() [with T = DER1]' is protected within this context
BASE<DER1> base;
0
On

In the BASE<DER1> instance there is no DER1 instance, but only a BASE that tries to cast itself to something that it isnt.

PS: this talk about the c++ memory model is quite related, but goes beyond what I could explain in simple words.