Search

12.3 — Virtual destructors, virtual assignment, and overriding virtualization

Virtual destructors

Although C++ provides a default destructor for your classes if you do not provide one yourself, it is sometimes the case that you will want to provide your own destructor (particularly if the class needs to deallocate memory). You should always make your destructors virtual if you’re dealing with inheritance. Consider the following example:

Note: If you compile the above example, your compiler may warn you about the non-virtual destructor (which is intentional for this example). You may need to disable the compiler flag that treats warnings as errors to proceed.

Because base is a Base pointer, when base is deleted, the program looks to see if the Base destructor is virtual. It’s not, so it assumes it only needs to call the Base destructor. We can see this in the fact that the above example prints:

Calling ~Base()

However, we really want the delete function to call Derived’s destructor (which will call Base’s destructor in turn), otherwise m_array will not be deleted. We do this by making Base’s destructor virtual:

Now this program produces the following result:

Calling ~Derived()
Calling ~Base()

Rule: Whenever you are dealing with inheritance, you should make any explicit destructors virtual.

As with normal virtual member functions, if a base class function is virtual, all derived overrides will be considered virtual regardless of whether they are specified as such. It is not necessary to create an empty derived class destructor just to mark it as virtual.

Virtual assignment

It is possible to make the assignment operator virtual. However, unlike the destructor case where virtualization is always a good idea, virtualizing the assignment operator really opens up a bag full of worms and gets into some advanced topics outside of the scope of this tutorial. Consequently, we are going to recommend you leave your assignments non-virtual for now, in the interest of simplicity.

Ignoring virtualization

Very rarely you may want to ignore the virtualization of a function. For example, consider the following code:

There may be cases where you want a Base pointer to a Derived object to call Base::getName() instead of Derived::getName(). To do so, simply use the scope resolution operator:

You probably won’t use this very often, but it’s good to know it’s at least possible.

Should we make all destructors virtual?

This is a common question asked by new programmers. As noted in the top example, if the base class destructor isn’t marked as virtual, then the program is at risk for leaking memory if a programmer later deletes a base class pointer that is pointing to a derived object. One way to avoid this is to mark all your destructors as virtual. But should you?

It’s easy to say yes, so that way you can later use any class as a base class -- but there’s a performance penalty for doing so (a virtual pointer added to every instance of your class). So you have to balance that cost, as well as your intent.

Conventional wisdom (as initially put forth by Herb Sutter, a highly regarded C++ guru) has suggested avoiding the non-virtual destructor memory leak situation as follows, “A base class destructor should be either public and virtual, or protected and nonvirtual.” A class with a protected destructor can’t be deleted via a pointer, thus preventing the accidental deleting of a derived class through a base pointer when the base class has a non-virtual destructor. Unfortunately, this also means the base class can’t be deleted through a base class pointer, which essentially means the class can’t be dynamically allocated or deleted except by a derived class. This also precludes using smart pointers (such as std::unique_ptr and std::shared_ptr) for such classes, which limits the usefulness of that rule (we cover smart pointers in a later chapter). It also means the base class can’t be allocated on the stack. That’s a pretty heavy set of penalties.

Now that the final specifier has been introduced into the language, our recommendations are as follows:

  • If you intend your class to be inherited from, make sure your destructor is virtual.
  • If you do not intend your class to be inherited from, mark your class as final. This will prevent other classes from inheriting from it in the first place, without imposing any other use restrictions on the class itself.

12.4 -- Early binding and late binding
Index
12.2a -- The override and final specifiers, and covariant return types

104 comments to 12.3 — Virtual destructors, virtual assignment, and overriding virtualization

  • koe

    "It also means the base class can’t be allocated on the stack."

    I'm not sure I understand this, aren't all normal objects allocated on the stack? Do you mean 'can't be allocated on the heap'? However, you already said they can't be dynamically allocated, and a derived class could certainly be allocated on the heap which implies the base class sub-object would also be allocated there.

    • nascardriver

      You could test it out yourself with a simple program. In fact, I did the same test earlier to get a better understanding, and found that the base class can’t be allocated on the stack.
      Haha, the turntables!
      The point of the protected destructor is to prevent destruction of of a derived object through a base pointer. We want to allow destruction of the base through a derived pointer, so the sub-object of a dynamically-allocated derived object is cool. The derived object is allowed to destruct the base portion, because it has access to protected members.

      • koe

        Fair play, I tested it as well. When I tried to make a temporary object in a derived class, where the parent class has a protected destructor, my compiler said "protected destructor can only be used to destroy a base class subobject". In other words, a class with a protected destructor can _only_ be inherited, and never instantiated on its own. Does that seem accurate?

  • Fang

    Hi,

    Suppose we have an inheritance chain like A -> B -> C -> D, where C dynamically allocate some resource. Then, we set a virtual destructor for C, but keep the rest as default. If we do something like

    would that successfully deallocate the memory? My thought is that only destructors of C and D would be declared virtual, while B would have the non-virtual one, hence fail to resolve to D's destructor. If this is indeed the case, does that mean we have to declare an empty virtual constructor on A (most base class)?

    • koe

      You could test it out yourself with a simple program. In fact, I did the same test earlier to get a better understanding, and found that (e.g. with your example) 'delete ptr' will call the implicit destructor B::~B(). If B::~B() is implicit, or explicit and non-virtual, then it won't resolve to the destructor D::~D().

      This reality lines up with the best practices recommended at the end of this chapter. Either:
      - make your classes 'final' so they can't be inherited from, which might cause memory leaks if the destructor isn't made virtual for performance reasons
      - or make your destructors 'virtual' to ensure memory safety, even if the destructor is empty

  • saj

    Hello, I compiled the code from this lesson

    gave me these errors :

    what does this mean, and how can i solve this?

    • nascardriver

      Those are warnings which your compiler is treating as errors (That's a good setting). Either temporarily disable "treat warnings as errors" in your compiler settings or do as the compiler says and add the missing functions.

      • saj

        After reading the message several times, what i understood is that because Derived is utilizing pointer data member, so compiler is complaining about assignment and copy constructors to avoid shallow copy of object.

        I fixed it by adding these two lines now it compiles fine without any error/warnings!

        correct me if i am wrong :)

        • nascardriver

          That's right! The problem with the shallow copy would be that both the original object and the copy try to delete `m_array` when they die

  • R

    Regarding this example -

    I understand why it doesn't work with non-virtual destructor, but I don't understand why anyone would use "delete base;" here instead of "delete derived;", since it's the derived pointer that had memory allocated to it. While it may be for the sake of example, I wonder if there are coding situations where this issue occurs more naturally.

    • nascardriver

      If you have mixed derived types stored in a container. The container can only store the Base type, because otherwise we wouldn't be able to mix types.

  • Ahmed Alkanaq

    In the example explaining the drawback of not making the destructor virtual, I can see how that could lead to a leaking memory through failure to proceed to an inherited class destructor.

    However, I couldn't understand the initialization of the the derived pointer int the main function:

    If the constructor of Derived accepts an int only to generate a pointer to an int array, why did we initialize the derived as a pointer and used new inside the initializer with the Derived constructor?

  • Machineman

    The last paragraph with Herb Sutter went over my head .Can you please give some example.

    • Kwonk

      When working with classes that contain virtual functions you need to keep two things in mind:
      (You'll learn about all of this in lesson "The virtual table")
      (1) The size of every class that defines or overrides a virtual function is increased by a the size of a pointer.
      (2) Every time a virtual function is called, the program takes LONGER to call that function than if it were a normal function, because it needs to look up which exact version of the function it needs to call, and thus it is a performance hit.

      Because of these two reasons, you're gonna want to keep the number of classes that use a virtual pointer as low as possible.

      But of course, there will be times when you need to use virtual pointers, especially when it comes to destructors, as this lesson teaches.

      (quick recap in my own words)
      Let's say you have a class "Parent"...

      Now, this is all well and good. But what if someone who is using your code, wanted to make his own type of Parent, (and they would therefore make a subclass, inhereting from Parent), it could look like this:

      Now, unlike with Parent, it's actually important a Child's destructor specifically is run when that Child object is deleted, as it removes dynamically allocated memory.
      This causes problems when you delete a Child via a Parent*.

      Now you probably got this much already, but this is where protected and public come into play:

      If Parent's destructor had been protected, the line

      would not have been allowed, because the outside world couldn't touch a Parent's destructor.
      So, this also means a Child cannot be deleted via a Parent pointer, because the program thinks it's dealing with a Parent whose destructor is hidden, which means that the whole is-it-a-Parent-or-a-Child--which-destructor-do-I-use problem would never occur.

      But still, there are occasions on which code on the outside does need to delete a Child via a Parent pointer, so then you HAVE TO make Parent::~Parent() public.

      Therefore you always have two options:
      (1) The destructor needs to be public.
          In that case, in order to prevent a Child being wrongly deleted by a Parent*, you make the destructor virtual, so that the program will look up which destructor to use, which takes time.

      (2) The destructor does not need to be public.
          In that case, since Child and Parent take care of their own deletion (somehow...), and the mixup with the pointers can't happen because a Parent (or what is thought to be a Parent) can't be deleted anyway, you don't have to make a the destructor virtual.

      And for the part about shared and unique pointers, you'll learn that in a later lesson. I reckon you don't need to worry about that now.

      The line "It also means the base class can’t be allocated on the stack."
      is about this:
      A protected destructor comes with another disadvantage:
      Since all objects need to be destructed eventually (they go out of scope, the program ends), this means every object's destructor needs to be called at some point. But if it's protected, the object can't be deleted. Therefore, you cannot construct an object with a protected/private destructor.
      (Although, you COULD do it using 'new'. However, you still couldn't use 'delete', which you'd also want to use eventually.)

  • Kwonk

    Hi Alex & nascardirver, one question I surprisingly couldn't really find the answer to anywhere in this chapter is (if there is, my mistake, please direct me to it):

    Is it possible and correct/safe to delete a dynamically allocated instance of a sub-class via a super-class pointer?

    And: is the answer to this question different when SuperParent and SubChild are in the virtual table?

    • nascardriver

      You're deleting an object with automatic storage duration. Line 15 should be

      You can check this yourself by adding destructors

      Only the parent gets deleted, there's no way for the compiler to know the real type of `ptr` and there is no run-time type information.
      To fix this, add a virtual destructor to your parent class (All children will get a virtual destructor automatically)

      Now the children will be destructed properly.

  • Maverick

    In one of the earlier lessons, it was said that the destructor of derived class is executed and then that of base class. So shouldn't the derived class destructor be executed first irrespective of the fact that the base class destructor is virtual or not?

    • nascardriver

      If you `delete` a `Base` pointer to a `Derived` class, the `delete` doesn't know what the type of the pointer actually is, it only sees `Base`, so that's what it will delete, the `Derived` portion gets leaked. If you make the destructor `virtual`, the `delete` will look up the destructor in the vtable and call the `Derived` destructor.

  • sajshaj

    I am working with g++ 4.4.7:
    I ran the program given in this with some modification and crash. I tried valgrind but does not seem to understand it thoroughly. Could you please help have some highlight on it

    • nascardriver

      g++4 is terribly old. Consider updating.
      You can only `delete` objects that you created with `new`. You're trying to `delete` `i`, which has automatic storage duration and died before you tried to `delete` it. See lesson P.6.9.

  • zuhail

    hey! nascarddriver said this wont work but its working in codeblocks why so?
    p.s.destructor in base class is virtual

  • Sergey

    Hi. I have a question below.

    Thanks a lot!

    • If the classes are using virtual destructors, line 6 will clear up everything.
      If the destructors are non-virtual, you're leaking everything that's in `Derived` but not in `Base`.
      I didn't look it up, but I'm fairly certain that accessing the derived portion after deleting the base portion is undefined behavior.
      Line 9 is a double free (You're trying to delete deleted memory), that won't work.

  • Andrei

    <<As with normal virtual member functions, if a base class function is virtual, all derived overrides will be considered virtual regardless of whether they are specified as such ..
    >>

    Maybe you meant: <<.., if a base class destructor is virtual, all>> It makes more sense to me in that context.

    Keep it up!!

  • Hi Alex!

    Snippet 1 line 32 has an extra space.
    This lesson isn't using brace initialization.

Leave a Comment

Put all code inside code tags: [code]your code here[/code]