3

I created the following test code to experiment with the Curiously Recurring Template Pattern, in which I have a Base class with an Interface() and a Derived class with an Implementation. It's modeled directly after the simplest example from the linked Wikipedia page.

#include <iostream>

template <class T>
class Base {
 public:
  void Interface() {
    std::cout << "Base Interface()" << std::endl;
    static_cast<T*>(this)->Implementation();
  }
};

class Derived : public Base<Derived> {
 public:
  Derived(int data = 0) : data_(data) {}

  void Implementation() {
    std::cout << "Derived Implementation(), data_ = " << data_ << std::endl;
  }

 private:
  int data_;
};

int main() {
  Base<Derived> b;
  b.Interface();
  std::cout << std::endl;

  Derived d;
  d.Interface();
  std::cout << std::endl;

  return 0;
}

Once compiled, the program runs smoothly and produces the following output:

Base Interface()
Derived Implementation(), data_ = -1450622976

Base Interface()
Derived Implementation(), data_ = 0

The interesting part is the first test, in which a Base pointer is being cast to a Derived pointer, from which the function Implementation() is being called. Inside this function, a "member variable" data_ is being accessed.

To us this is nonsense, but to a compiler it is simply the value at some offset from this object's memory location. However, in this case the Derived class takes more space than the base class, and so if the compiler thinks it's accessing a data member from a Derived object, but actually the object is a Base object, then the memory we are accessing may not belong to this object, or even to this program.

It seems that this programming practice allows us (the programmer) to very easily do very dangerous things, such as making a seemingly reasonable function call that ends up reading from an uncontrolled memory location. Have I interpreted the mechanics of this example correctly? If so, are there techniques within the CRTP paradigm that I've missed to ensure this problem will not show up?

Apollys supports Monica
  • 2,938
  • 1
  • 23
  • 33

2 Answers2

0

In general static_cast works properly if the pointer you pass there points to an object which contains the target class somewhere in its inheritance hierarchy.

For
Base b; b.Interface();

a pointer to a real Base object is passed to static_cast, and it is not related to Derived class at all. So after casting you have a pointer that looks like a pointer to Derived, but it still points to a Base object in memory. When you access the data_ member via this pointer, you get a content of some uninitialized area in memory.

0

I only recently came to the same realization as you did.

After a bit of searching, my answer is yes, you did interpreted the CRTP correctly, and not, you did not miss techniques to avoid the issue. Which we both find surprising.

Even more surprising for me, was the fact that the code compiles. It is a static downcasting of a base-class pointer, where it is known at compile time that the actual instance is of the base class. But apparently these casts are allowed, they just lead to undefined behaviors in cases like this one (e.g. see here )

As suggested in the comments by @NathanOliver, a solution to avoid the risk is to make the constructor of the base class protected.

L. Bruce
  • 170
  • 7