Is it ever safe to call a virtual method from a constructor?

110 views Asked by At

I am aware that it is unsafe to call a virtual method from within the constructor of the class in which the method is declared virtual (or pure virtual).

However, I am wondering if the following is safe:

class Base
{
public:
    void helper()
    {
        this->foo();
    }

protected:
     virtual void foo() = 0;
};

class Derived : public Base
{
public:
    Derived()
    {
        this->helper();
    }

protected:
    void foo() override
    {
        //...
    }
};

Yes, the virtual method foo() is called from within a constructor, but it is called from within the constructor for the class which implements foo().

Here is a working example: https://godbolt.org/z/nz4YsbqYT

But, is this well-defined by the standard for all compilers?

2

There are 2 answers

0
Ted Lyngmo On BEST ANSWER

I am wondering if the following is safe:

Yes, in the example you've given, it is.

... is this well-defined for all compilers by the standard?

Yes. *this is a Derived in the constructor of Derived, so Derived::foo will be called from helper.

Safe? Well... If you expect everyone to know how it works, it is. Someone may however do:

class EvenMoreDerived : public Derived {
protected:
    void foo() override {
        std::cout << "not called\n";
    }
};

... and, without other additions, expect this version of foo to be called when instantiating an EvenMoreDerived. That will of course not happen since the call to helper is done in the Derived constructor, so it'll still be Derived::foo that's being called, not EvenMoreDerived::foo.

One possible way to avoid mistakes like that if you want to call virtual methods in the constructor is to make the class final:

class Derived final : public Base { // the above mistake will not be so easy to make
};

... or make foo final:

class Derived : public Base {
protected:
    void foo() override final {
    }
};
0
MSalters On

This is all very well-defined. The "problem" is simply that the this pointer in the Base constructor points to a Base object, as the Derived part has not been constructed yet. That's why you can't call Derived::foo() from the Base constructor; there's no Derived object to call it on. No special rule, it's just the regular C++ rule that you can't call methods of an object that you haven't yet created (or which you already destroyed, in the case of destructors).

All compilers agree on this; it's not that complex a rule. It's also pretty consistent. The Base constructor could throw before the Derived constructor can even run, so you can't even say for sure that there will be a Derived object eventually. A required resource might be lacking.

But in your case, there's not even such a problem. You're calling Derived::foo from the Derived constructor, and that's perfectly OK. All base classes and members of Derived exist and are initialized at that point.