-1

Consider following snippet of code making using of CRTP

#include <iostream>

struct Alone
{
    Alone() { std::cout << "Alone constructor called" << std::endl; }
    int me {10};
};

struct Dependant
{
    explicit Dependant(const Alone& alone)
        : ref_alone(alone)
    { std::cout << "Dependant called with alone's me = " << alone.me << std::endl; }
    const Alone& ref_alone;
    void print() { std::cout << ref_alone.me << std::endl; }
};

template <typename D>
struct Base
{
    Base() { std::cout << "Base constructor called" << std::endl; }

    D* getDerived() { return static_cast<D*>(this); }
    Dependant f { getDerived()->alone };
    void print() { f.print(); }
};

struct Derived : Base <Derived>
{
    Derived() { std::cout << "Derived constructor called "  << std::endl; }
    Alone alone {};
    void print() { Base::print(); };
};

int main()
{
    Derived d;
    d.print();
}

original link http://coliru.stacked-crooked.com/a/79f8ba2d9c38b965

I have a basic question first

  • How does memory allocation happen when using inheritance? I know the constructors are called from Base to Derived, but it seems that when I do

    Derived d;

    memory equivalent to sizeof(D) is allocated, and then constructors are called. Is my understanding correct here? (This would explain printing uninitialised member)

  • Considering the above example in mind, would you suggest/recommend any best practices when it comes to CRTP?

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
skgbanga
  • 2,477
  • 2
  • 20
  • 29
  • You're using a static initializer on a member variable, I'm surprised that even compiles. The proper initialization is to put it into the constructor initializer list: `Alone() : me(10) { ... }` – Mark Ransom Apr 19 '16 at 03:27
  • Post the example code. – Cheers and hth. - Alf Apr 19 '16 at 03:45
  • @markRansom umm.. what? Care to explain this a bit? (provide a link, if any). I have been doing this for quite a while, and it has worked appropriately. – skgbanga Apr 19 '16 at 04:05
  • @skgbanga: He's basically pointing out the fact that you didn't tag your question C++11, and default member initializers are a C++11 feature. But quite frankly, this is 2016, and that shouldn't be necessary. – Nicol Bolas Apr 19 '16 at 04:07
  • @NicolBolas that's right, I'm kind of stuck in the past. C++11 has so much stuff in it, I'm still catching up. This is the first time I've been exposed to this syntax. – Mark Ransom Apr 19 '16 at 04:37

1 Answers1

1

memory equivalent to sizeof(D) is allocated, and then constructors are called

How else could it possibly work? You can't construct an object in memory that isn't allocated yet. Memory allocation always comes before object construction.

Considering the above example in mind, would you suggest/recommend any best practices when it comes to CRTP?

The standard practices for the CRTP: don't call into the CRTP in a constructor/destructor. This is also true of virtual functions. Virtuals are dynamic polymorphism, while CRTP is static polymorphism. But they're both using the same basic mechanism: a base class that defines an interface that the derived class must implement.

And just like with virtual functions, trying to call it in constructors/destructors won't do what you mean. The only difference is that with virtual functions, the compiler will actually keep you from getting undefined behavior. Whereas with the CRTP, you just get breakage.

Note that this includes default member initializers, which for the purpose of non-aggregates are just shorthand for constructor initialization lists.

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
  • "How else could it possibly work?" I think I was not clear in explaining my question. When I said memory equivalent to sizeof(D), what I meant was, Is the ENTIRE memory for that object allocated at once and then construction is done piece-wise, or allocation and construction both are done piece wise but allocated memory is guaranteed to be contiguous. (An unlikely possibility though) – skgbanga Apr 19 '16 at 04:19
  • 1
    @skgbanga: When you allocate an object with `malloc`, you pass it the whole size of the actual object. When you allocate an object with `new`, `operator new` is passed the whole size of the object being allocated. Why do you think this would be different for a stack object? – Nicol Bolas Apr 19 '16 at 05:02
  • I didn't actually, but I wanted to confirm from someone. Thanks for your answer. One followup questions: is there a programmatic way in which I can ensure that whenever someone tries to "access Derived's members in constructor/destructor of base" I get a compilation error? – skgbanga Apr 19 '16 at 13:15