I have the following code:
class IInterface {
public:
virtual ~IInterface() {}
virtual void doSomething() = 0;
};
class Concrete : public IInterface {
public:
Concrete() : data{0} {doSomething();}
void doSomething() override {std::cout << "I am doing something concrete" << std::endl;}
private:
int data;
};
When I compile, Clang gives the following output:
Call to virtual method doSomething during construction bypasses virtual dispatch
Note that if in the Concrete's constructor I do Concrete::doSomething()
, the message goes away.
Say we do Concrete c;
. This is what I think it happens:
c
is allocated.__vptr
is pointing to a pure virtual function.Concrete
constructor is called. Initialization of any non-static members take place. Initialization complete, the __vptr
now points to Concrete
's implementation of doSomething()
. The constructor body is ran and it should call the overridden doSomething()
.Is anything else than what I describe happening?
EDIT
It is clang-tidy that gives the message LLVM version 17.0.6
There's nothing inherently wrong with calling virtual functions from constructors or destructors, and doing so does not bypass virtual dispatch. People who say that are oversimplifying what's really going on.
struct base {
virtual void f() { }
void g() { f(); }
base() {
f(); // always calls base::f
g(); // always calls base::f
}
};
struct intermediate : base {
void f();
intermediate() {
f(); // always calls intermediate::f
g(); // always calls intermediate::f via virtual dispatch
}
};
The true rule is simply that virtual function calls in constructors (and destructors) are dispatched according to the rules for the class whose constructor is currently running.
intermediate i;
// base::base calls to f() and g() both call base::f
// intermediate::intermediate calls to f() and g() both call intermediate::f
i.g(); // calls intermediate::f
People tend to get confused when there's another level of construction:
struct derived : intermediate {
derived() : intermediate() { }
void f() { }
};
derived d; // base::base() calls to f() and g() both call base::f
// intermediate::intermediate() calls to f() and g() both call intermediate::f
d.f(); // calls derived::f
d.g(); // cals derived::f
The key here is that during the construction of the intermediate
subobject all virtual calls act as if they were being called on an object of type intermediate
, even though the type of the actual object being constructed is derived
. There's no derived
in the constructor for intermediate
.
None of the calls to f()
"bypass" virtual dispatch. The virtual function calls go to the version of f()
that's defined for the class whose constructor is running. Folks who characterize this as "bypassing" virtual dispatch are begin lazy. Folks who claim that calling virtual functions from constructors and destructors is inherently dangerous are being alarmist. Yes, you have to understand what's going on. No, there are no demons lurking here.