Home > Blockchain >  C Primer 5th Ed - Stanley : Can a base class with empty destructor making it virtual?
C Primer 5th Ed - Stanley : Can a base class with empty destructor making it virtual?

Time:12-20

I am reading the below excerpts from the above book (chapter 15 - Object Oriented Programming Section 15.7.1 Virtual Destructors).

Destructors for base classes are an important exception to the rule of thumb that if a class needs a destructor, it also needs copy and assignment (§13.1.4, p. 504). A base class almost always needs a destructor, so that it can make the destructor virtual.

If a base class has an empty destructor in order to make it virtual, then the fact that the class has a destructor does not indicate that the assignment operator or copy constructor is also needed.

I don't understand the part where it mentioned "if a base class has an empty destructor, so that I can make it virtual". I thought the only way to make it virtual on a base class is to have the keyword "virtual".

Any explanation be great!

CodePudding user response:

The author here considers an empty virtual destructor to be a destructor that is still user-declared (not one that is implicitly-declared as virtual because of a base class destructor) and either defined with an empty body or defaulted:

virtual ~MyClass() = default;

or

virtual ~MyClass() {}

It is impossible to force an implicitly-declared destructor to be virtual (if there isn't already a base class with a virtual destructor) and so either of these are the only possibilities to make the destructor virtual but have it behave the same way that the implicitly-defined (non-virtual) one would. That's why the author says "in order to make it virtual".


The quote is saying that having either of these in a class doesn't indicate that the class semantically needs to define its own copy assignment operator or copy constructor. The destructor still does exactly the same as if it was implicitly-defined and so the implicitly-defined copy operations are likely to still be semantically correct.

A non-empty destructor, e.g. one that performs some non-trivial work with the class (not just something like simple logging etc.) is usually an indication that the implicitly defined copy operations will not be semantically correct for the intended behavior of the class, which is why there is the mentioned rule-of-three saying that in such situations copy operations should be manually defined as well to avoid the unintended semantics.


However, in connection with move semantics there is still a problem with an empty virtual destructor. Declaring any destructor in any way will inhibit implicit declaration of the move constructor and move assignment operator.

As a consequence, if your class has e.g. a std::vector member, it will become inefficient, since it can never be moved, only copied. Usually a class with virtual destructor is used polymorphically and not copied/moved around, but that is not always the case. So it may make sense to still explicitly default all move and copy operations (in line with the rule-of-five) in such a situation:

virtual ~MyClass() = default;
MyClass(const MyClass&) = default;
MyClass(MyClass&&) = default;
MyClass& operator=(const MyClass&) = default;
MyClass& operator=(MyClass&&) = default;

Or (probably more likely), given that the class may be intended to only be used polymorphically, explicitly delete all of them to make it impossible for object slicing to occur unintentionally (see C core guidelines C.67):

virtual ~MyClass() = default;
MyClass(const MyClass&) = delete;
MyClass(MyClass&&) = delete;
MyClass& operator=(const MyClass&) = delete;
MyClass& operator=(MyClass&&) = delete;

CodePudding user response:

First, it means that, for a base class Base, you'd likely need a virtual destructor:

class Base {
public:
    virtual ~Base() {} // note the empty body
};

This is so that deleting a descendant via base pointer calls the proper ctor and frees memory correctly. Note that copy/assignment is not needed here; at this point, we don't even know if the descendant class is expected to have data members or simply fulfills an interface without any data.

This is, however, only true if you're using naked and some of the smart pointers (e.g. std::unique_ptr<>). When you use std::shared_ptr<>, your destructor is automatically saved so you don't need virtual destructors to keep a std::shared_ptr<Descendant> in a std::shared_ptr<Base> before destroying it.

  • Related