SeniorDebuggingOccasionalNot answered yet
Why doesn't a virtual call inside a constructor dispatch to the derived override?
The author intends the Widget constructor to call Button's overridden render(), but the program prints Widget::render instead of Button::render.
#include <iostream>
struct Widget {
Widget() {
render(); // intended: Button::render
}
virtual void render() const {
std::cout << "Widget::render\n";
}
};
struct Button : Widget {
std::string label_ = "OK";
void render() const override {
std::cout << "Button::render label=" << label_ << "\n";
}
};
int main() {
Button b;
}
Identify the bug and explain the cause.
During each constructor stage the object's vptr points to that class's own vtable, because more-derived parts do not exist yet. A virtual call from the base constructor resolves to the base version. Calling a pure virtual this way is undefined behavior.
- ✗Calling virtual hooks from a base constructor expecting derived behavior to run
- ✗Calling a pure virtual from a constructor or destructor — undefined behavior, not just the base version
- ✗Trying to fix it with
dynamic_cast<Derived*>(this)inside the base constructor
- →Does the same rule apply to virtual calls inside a destructor, and why?
- →How does a two-phase init or a factory function solve this cleanly?
Contents
The problem
#include <iostream>
struct Widget {
Widget() {
// intent: call the overridden render() from Button
render(); // (1) dispatches to Widget::render
}
virtual void render() const {
std::cout << "Widget::render\n";
}
};
struct Button : Widget {
std::string label_ = "OK";
void render() const override {
std::cout << "Button::render label=" << label_ << "\n";
}
};
int main() {
Button b; // prints "Widget::render", not Button::render
}
At point (1) the object's dynamic type is still Widget: the vptr points at the Widget vtable, and the Button subobject (including label_) does not exist yet. If render() were pure virtual, the call would be undefined behavior, not merely a dispatch to the base version.
The fix — two-phase initialization
struct Widget {
Widget() = default; // constructor makes no virtual calls
virtual void render() const { std::cout << "Widget::render\n"; }
virtual ~Widget() = default;
};
struct Button : Widget {
std::string label_ = "OK";
void render() const override {
std::cout << "Button::render label=" << label_ << "\n";
}
};
template <class T, class... Args>
T make() { // factory: object is fully constructed
T obj(std::forward<Args>(args)...); // before render() is called
obj.render(); // dynamic type is now final
return obj;
}Contents