Is it allowed to pass "this" of derived class to constructor of base class?

76 views Asked by At

Here is the code I originally wanted to write:

class A {
public:
    A(someType someData) {
        this->init(someData);
    }
   
    virtual void init(someType) = 0;
}

This is not allowed, because at the point when base class constructor is called, instance of the derived class doesn't exist yet. So I changed it to this:

class A {
public:
    A(someType someData, A* derived) {
        derived->init(someData);
    }
   
    virtual void init(someType) = 0;
}

class B : public A {
public:
    B(someType someData)
    : A(someData, this) {}
    
}

The code compiles and runs without crashing but is it allowed or UB?

2

There are 2 answers

0
Alan Birtles On BEST ANSWER

No, it's just as undefined as calling the virtual function. The instance of B isn't constructed until after As constructor completes so you can't call virtual functions on it.

One approach to fix this is to do a two stage construction, fully construct the class, and only then, call the virtual method:

class A {
public:
    virtual void init(someType) = 0;

    template <typename T>
    static T construct(someType someData)
    {
       T obj;
       obj.init(someData);
       return obj;
    }
}

class B : public A {
public:
    void init(someType) override
    {}
}

int main()
{
  B b = A::construct<B>();
}
0
Quuxplusone On
class A {
public:
    explicit A(someType someData, A* derived) {
        derived->init(someData);
    }
    virtual void init(someType) = 0;
};

class B : public A {
public:
    explicit B(someType someData)
        : A(someData, this) {}
    void init(someType) override;
};

My understanding is that B's constructor can certainly load this and pass it to A's constructor... but, before the function body of B's constructor has been entered, that this pointer will still point to only an A object. (In fact, before A's constructor's function body has been entered, it won't even point to an A yet!)

So when you call derived->init() on A* derived, derived points to an A-that-is-not-yet-a-B. In particular, it still has A's vptr, pointing to A's vtable, where A::init is pure virtual. It has no idea that it ought to be calling B::init instead. So you have a call to a pure virtual function, which is UB — and in practice will just abort the program.

In fact you could have tried this out and seen for yourself, without even asking StackOverflow. :) Godbolt.