Why should I declare a virtual destructor for an abstract class in C++?
Asked Answered
P

7

181

I know it is a good practice to declare virtual destructors for base classes in C++, but is it always important to declare virtual destructors even for abstract classes that function as interfaces? Please provide some reasons and examples why.

Pegeen answered 7/11, 2008 at 0:55 Comment(0)
M
220

It's even more important for an interface. Any user of your class will probably hold a pointer to the interface, not a pointer to the concrete implementation. When they come to delete it, if the destructor is non-virtual, they will call the interface's destructor (or the compiler-provided default, if you didn't specify one), not the derived class's destructor. Instant memory leak.

For example

class Interface
{
   virtual void doSomething() = 0;
};

class Derived : public Interface
{
   Derived();
   ~Derived() 
   {
      // Do some important cleanup...
   }
};

void myFunc(void)
{
   Interface* p = new Derived();
   // The behaviour of the next line is undefined. It probably 
   // calls Interface::~Interface, not Derived::~Derived
   delete p; 
}
Marja answered 7/11, 2008 at 1:1 Comment(4)
delete p invokes undefined behaviour. It is not guaranteed to call Interface::~Interface.Simonson
@Mankarse: can you explain what causes it to be undefined? If Derived didn't implement its own destructor, would it still be undefined behavior?Stere
@Wallacoloo: It is undefined because of [expr.delete]/: ... if the static type of the object to be deleted is different from its dynamic type, ... the static type shall have a virtual destructor or the behavior is undefined. .... It would still be undefined if Derived used an implicitly generated destructor.Simonson
Since this is the top answer, just adding a really basic clarification that the fix here is to add to class Interface virtual ~Interface().Enrika
E
37

The answer to your question is often, but not always. If your abstract class forbids clients to call delete on a pointer to it (or if it says so in its documentation), you are free to not declare a virtual destructor.

You can forbid clients to call delete on a pointer to it by making its destructor protected. Working like this, it is perfectly safe and reasonable to omit a virtual destructor.

You will eventually end up with no virtual method table, and end up signalling your clients your intention on making it non-deleteable through a pointer to it, so you have indeed reason not to declare it virtual in those cases.

[See item 4 in this article: http://www.gotw.ca/publications/mill18.htm]

Expand answered 7/11, 2008 at 2:39 Comment(5)
The key to making your answer work is "on which delete is not called upon." Usually if you have an abstract base class designed to be an interface, delete is going to be called on the interface class.Tempera
As John above pointed out, what you're suggesting is pretty dangerous. You're relying on the assumption that clients of your interface will never destroy an object knowing only the base type. The only way you could guarantee that if it's nonvirtual is to make the abstract class's dtor protected.Expunge
Michel, i have said so :) "If you do that, you make your destructor protected. If you do so, clients will not be able to delete using a pointer to that interface." and indeed it's not relying on the clients, but it has to enforce it telling the clients "you cannot do..." . I don't see any dangerExpand
i fixed the poor wording of my answer now. it states it explicitly now that it doesn't rely on the clients. actually i thought that's obvious that relying on clients doing something is out the way anyway. thanks :)Expand
+1 for mentioning protected destructors, which are the other "way out" of the problem of accidentally calling the wrong destructor when deleting a pointer to a base class.Kayser
S
26

I decided to do some research and try to summarise your answers. The following questions will help you to decide what kind of destructor you need:

  1. Is your class intended to be used as a base class?
    • No: Declare public non-virtual destructor to avoid v-pointer on each object of the class *.
    • Yes: Read next question.
  2. Is your base class abstract? (i.e. any virtual pure methods?)
    • No: Try to make your base class abstract by redesigning your class hierarchy
    • Yes: Read next question.
  3. Do you want to allow polymorphic deletion through a base pointer?
    • No: Declare protected virtual destructor to prevent the unwanted usage.
    • Yes: Declare public virtual destructor (no overhead in this case).

I hope this helps.

* It is important to note that there is no way in C++ to mark a class as final (i.e. non subclassable), so in the case that you decide to declare your destructor non-virtual and public, remember to explicitly warn your fellow programmers against deriving from your class.

References:

Subtrahend answered 9/7, 2011 at 11:18 Comment(1)
This answer is partly outdated, there is now a final keyword in C++.Bedwarmer
R
11

Yes it is always important. Derived classes may allocate memory or hold reference to other resources that will need to be cleaned up when the object is destroyed. If you do not give your interfaces/abstract classes virtual destructors, then every time you delete a derived class instance via a base class handle your derived class' destructor will not be called.

Hence, you're opening up the potential for memory leaks

class IFoo
{
  public:
    virtual void DoFoo() = 0;
};

class Bar : public IFoo
{
  char* dooby = NULL;
  public:
    virtual void DoFoo() { dooby = new char[10]; }
    void ~Bar() { delete [] dooby; }
};

IFoo* baz = new Bar();
baz->DoFoo();
delete baz; // memory leak - dooby isn't deleted
Rataplan answered 7/11, 2008 at 1:1 Comment(1)
True, in fact in that example, it may not just memory leak, but possibly crash :-/Bulkhead
B
7

It is not always required, but I find it to be good practice. What it does, is it allows a derived object to be safely deleted through a pointer of a base type.

So for example:

Base *p = new Derived;
// use p as you see fit
delete p;

is ill-formed if Base doesn't have a virtual destructor, because it will attempt to delete the object as if it were a Base *.

Bulkhead answered 7/11, 2008 at 1:2 Comment(6)
don't you want to fix boost::shared_pointer p(new Derived) to look like boost::shared_pointer<Base> p(new Derived); ? maybe ppl will understand your answer then and voteExpand
EDIT: "Codified" a couple of parts to make the angle brackets visible, as litb suggested.Kayser
@EvanTeran: I'm not sure whether this has changed since the answer was originally posted (the Boost documentation at boost.org/doc/libs/1_52_0/libs/smart_ptr/shared_ptr.htm suggests it may have), but it's not true these days that shared_ptr will attempt to delete the object as if it were a Base * - it remembers the type of the thing you created it with. See the referenced link, in particular the bit that says "The destructor will call delete with the same pointer, complete with its original type, even when T does not have a virtual destructor, or is void."Machinegun
@StuartGolodetz: Hmm, you may be right, but I'm honestly not sure. It may still be ill formed in this context due to lack of virtual destructor. It's worth looking into through.Bulkhead
@EvanTeran: In case it's helpful - https://mcmap.net/q/48049/-how-is-it-possible-if-it-is-to-implement-shared_ptr-without-requiring-polymorphic-classes-to-have-virtual-destructor.Machinegun
Good enough to convince me, i'll edit my answer here to reflect that :-)Bulkhead
E
5

It's not only good practice. It is rule #1 for any class hierarchy.

  1. The base most class of a hierarchy in C++ must have a virtual destructor

Now for the Why. Take the typical animal hierarchy. Virtual destructors go through virtual dispatch just as any other method call. Take the following example.

Animal* pAnimal = GetAnimal();
delete pAnimal;

Assume that Animal is an abstract class. The only way that C++ knows the proper destructor to call is via virtual method dispatch. If the destructor is not virtual then it will simply call Animal's destructor and not destroy any objects in derived classes.

The reason for making the destructor virtual in the base class is that it simply removes the choice from derived classes. Their destructor becomes virtual by default.

Envisage answered 7/11, 2008 at 1:3 Comment(6)
I mostly agree with you, because usually when defining a hierarchy you want to be able to refer to a derived object using a base class pointer/reference. But that's not always the case, and in those other cases, it may suffice to make the base class dtor protected instead.Kayser
@Kayser making it protected won't protect you from incorrect internal deletesEnvisage
@JaredPar: That's right, but at least you can be responsible in your own code -- the hard thing is to make sure that client code can't cause your code to explode. (Similarly, making a data member private doesn't prevent internal code from doing something stupid with that member.)Kayser
@j_random_hacker, sorry to respond with a blog post but it really fits this scenario. blogs.msdn.com/jaredpar/archive/2008/03/24/…Envisage
@JaredPar: Excellent post, I agree with you 100%, especially about checking contracts in retail code. I just mean that there are cases when you know you don't need a virtual dtor. Example: tag classes for template dispatch. They have 0 size, you only use inheritance to indicate specialisations.Kayser
@j_random_hacker, I agree there are definitely cases. For example if you control an API that's internal to your program then you can eventually achieve a level of confidence it's not needed. But how can you impart this constraint onto your future coworkers? That's the question I struggle withEnvisage
L
4

The answer is simple, you need it to be virtual otherwise the base class would not be a complete polymorphic class.

    Base *ptr = new Derived();
    delete ptr; // Here the call order of destructors: first Derived then Base.

You would prefer the above deletion, but if the base class's destructor is not virtual, only the base class's destructor will be called and all data in derived class will remain undeleted.

Leucotomy answered 27/12, 2012 at 14:27 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.