Inheriting library classes
Many times, there is a need to add some additional functionality to existing classes, e.g. adding toUpper
- a method to std::string
to provide uppercased letters. In such cases, the author may inherit the standard library classes, e.g. std::string, std::vector
for the additional methods.
This way of extending the standard library classes has limitations and problems, as discussed below.
Challenges
The standard library classes don’t provide virtual destructors for performance reasons. Read more on this here.
Not having virtual destructors has several advantages
Reduces the size of the instance (vtable for virtual destructor will be missing)
Construction and destruction will be faster
The lack of virtual destructors poses the following challenges when inheriting.
The dynamic dispatch for the destructor won’t be called for the derived class.
This may lead to resource leaks (server connection, open file descriptor)
Followup thoughts
Q. One may think, given there is a lack of virtual destructors in standard library classes, why are they not marked as
final
?Backward compatibility
As the C++ standard has evolved over the years across iterations, the
final
keyword was added in C++11. Marking the classes final would mean the older code would become incompatible with recent versions. This should not be the case.Design Flexibility
One of the core philosophies of C++ is to provide programmers with a high degree of control and flexibility. Not marking classes as
final
allows for extensibility.So technically, the standard library does not enforce the closeness of classes, although it is recommended not to use inheritance.
Performance
While marking classes as
final
can potentially enable certain compiler optimizations (such as de-virtualization of function calls), the impact is more nuanced in practice.The decision to use
final
should be based on specific performance considerations and design goals of a class.The standard library aims to provide general-purpose functionality that is widely applicable, so the design decisions tend to favour flexibility over specific optimization opportunities that might only benefit certain use cases.