Discussion: A Destructive Relationship

Execute the code to understand the output and gain insights into destructors and virtual functions.

Run the output

Now, it’s time to execute the code and observe the output.

Press + to interact
#include <iostream>
#include <memory>
struct Widget
{
virtual void draw() { std::cout << "Widget draw\n"; }
virtual ~Widget() { std::cout << "Widget destructor\n"; }
};
struct Button : public Widget
{
void draw() override { std::cout << "Button draw\n"; }
~Button() override { std::cout << "Button destructor\n"; }
};
int main()
{
std::unique_ptr<Widget> widget = std::make_unique<Button>();
widget->draw();
}

Understanding the output

When we call draw(), the program only prints Button draw. But when unique_ptr goes out of scope, and the destructor is called, the program prints both Button destructor and Widget destructor. Why is that?

Virtual functions

A common technique for dynamic polymorphism is using virtual functions. We declare a class with one or more functions marked as virtual, allowing other classes to override those functions. When you then call a virtual function on a base class pointer pointing to an object of a derived class, the object’s dynamic type is used, and the overridden function is called instead.

Example

In this example, imagine a UI framework with a Widget class with some common behavior and concrete UI elements like Button, Text, and Image, which overrides the drawing behavior in the virtual draw function.

When we call draw() on the base class pointer to Widget, which is actually pointing to a derived class Button, Button::draw() is called instead of Widget::draw(), and Button draw is printed. However, for the destructor, the behavior is different; we can see that it first prints Button destructor and then also prints Widget destructor. What’s going on?

When an object is destroyed, all parts of it have to be destroyed. In this case, Button inherits from Widget, so each Button object consists of a Widget object in addition to any members from Button (none, in this case). We say that Button has a Widget subobject. If Button had a member of type Widget instead of inheriting from it, it would also have a Widget subobject. In either case, the subobjects must also be destroyed when the complete object is destroyed to avoid resource leaks. Luckily, C++ handles this for us; we don’t need to manually call the Widget destructor from the Button destructor.

Note also that the destructor’s body executes first, and only then are the subobject destructors called. This ensures that we can still use members and base classes in our class’s destructor. So, the program first prints Button destructor and then Widget destructor.

Question

What would happen if Widget did not declare its destructor as virtual, and in which scenarios would that be a problem?

Show Answer

Recommendations

Thanks to the automatic destruction of subobjects, a good technique to handle resources in C++ is to have Resource Acquisition Is Initialization (RAII) types (for instance unique_ptrs) as members. This technique also works if we derive from such a class.

Level up your interview prep. Join Educative to access 70+ hands-on prep courses.