Discussion: A Destructive Relationship
Execute the code to understand the output and gain insights into destructors and virtual functions.
We'll cover the following
Run the output
Now, it’s time to execute the code and observe the output.
#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
.
What would happen if Widget
did not declare its destructor as virtual
,
and in which scenarios would that be a problem?
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.