Why base class destructor (virtual) is called when a derived class object is deleted?

49,906

Solution 1

The Standard says

After executing the body of the destructor and destroying any automatic objects allocated within the body, a destructor for class X calls the destructors for X’s direct non-variant members,the destructors for X’s direct base classes and, if X is the type of the most derived class (12.6.2), its destructor calls the destructors for X’s virtual base classes. All destructors are called as if they were referenced with a qualified name, that is, ignoring any possible virtual overriding destructors in more derived classes. Bases and members are destroyed in the reverse order of the completion of their constructor (see 12.6.2). A return statement (6.6.3) in a destructor might not directly return to the caller; before transferring control to the caller, the destructors for the members and bases are called. Destructors for elements of an array are called in reverse order of their construction (see 12.6).

Also as per RAII resources need to be tied to the lifespan of suitable objects and the destructors of respective classes must be called upon to release the resources.

For example the following code leaks memory.

 struct Base
 {
       int *p;
        Base():p(new int){}
       ~Base(){ delete p; } //has to be virtual
 };

 struct Derived :Base
 {
       int *d;
       Derived():Base(),d(new int){}
       ~Derived(){delete d;}
 };

 int main()
 {
     Base *base=new Derived();
     //do something

     delete base;   //Oops!! ~Base() gets called(=>Memory Leak).
 }

Solution 2

Constructor and destructor are different from the rest of regular methods.

Constructor

  • can't be virtual
  • in derived class you either call explicitly constructor of base class
  • or, in case where you don't call base class constructor compiler will insert the call. It will call the base constructor without parameters. If no such constructor exists then you get compiler error.

struct A {};
struct B : A { B() : A() {} };

// but this works as well because compiler inserts call to A():
struct B : A { B() {} };

// however this does not compile:
struct A { A(int x) {} };
struct B : A { B() {} };

// you need:
struct B : A { B() : A(4) {} };

Destructor:

  • when you call destructor on derived class over a pointer or a reference, where the base class has virtual destructor, the most derived destructor will be called first and then the rest of derived classes in reversed order of construction. This is to make sure that all memory has been properly cleaned. It would not work if the most derived class was called last because by that time the base class would not exists in memory and you would get segfault.

struct C
{
    virtual ~C() { cout << __FUNCTION__ << endl; }
};

struct D : C
{
    virtual ~D() { cout << __FUNCTION__ << endl; }
};

struct E : D
{
    virtual ~E() { cout << __FUNCTION__ << endl; }
};

int main()
{
    C * o = new E();
    delete o;
}

output:

~E
~D
~C

If the method in base class is marked as virtual all the inherited methods are virtual as well so even if you don't mark the destructors in D and E as virtual they will still be virtual and they still get called in the same order.

Solution 3

This is by design. The destructor on the base class must be called in order for it to release its resources. Rule of thumb is that a derived class should only clean up its own resources and leave the base class to clean up itself.

From C++ spec:

After executing the body of the destructor and destroying any automatic objects allocated within the body, a destructor for class X calls the destructors for X’s direct members, the destructors for X’s direct base classes and, if X is the type of the most derived class (12.6.2), its destructor calls the destructors for X’s virtual base classes. All destructors are called as if they were referenced with a qualified name, that is, ignoring any possible virtual overriding destructors in more derived classes. Bases and members are destroyed in the reverse order of the completion of their constructor (see 12.6.2).

Also, because there is only one destructor, there is no ambiguity as to which destructor a class must call. This is not the case for constructors, where a programmer must pick which base class constructor should be called if there isn't an accessible default constructor.

Solution 4

A base class destructor may be responsible for cleaning up resources that were allocated by the base class constructor.

If your base class has a default constructor (one that doesn't take parameters or has defaults for all its parameters) that constructor is automatically called upon construction of a derived instance.

If your base class has a constructor that requires parameters, you must call it manually in the initializer list of the derived class constructor.

Your base class destructor will always be automatically called upon deletion of the derived instance since destructors don't take parameters.

If you're using polymorphism and your derived instance is pointed to by a base class pointer, then the derived class destructor is only called if the base destructor is virtual.

Solution 5

Because that's how dtor's work. When you create an object, ctors are invoked starting from the base, and going all the way to the most derived. When you destroy objects (correctly) the reverse happens. The time that making a dtor virtual makes a difference is if/when you destroy an object via a pointer (or reference, though that's fairly unusual) to the base type. In that case, the alternative isn't really that only the derived dtor gets invoked -- rather, the alternative is simply undefined behavior. That make happen to take the form of invoking only the derived dtor, but it might take an entirely different form as well.

Share:
49,906
KedarX
Author by

KedarX

Updated on July 31, 2020

Comments

  • KedarX
    KedarX almost 4 years

    A difference between a destructor (of course also the constructor) and other member functions is that, if a regular member function has a body at the derived class, only the version at Derived class gets executed. Whereas in case of destructors, both derived as well as base class versions get executed?

    It will be great to know what exactly happens in case of destructor (maybe virtual) & constructor, that they are called for all its base classes even if the most derived class object is deleted.

    Thanks in advance!

  • Igor Zevaka
    Igor Zevaka almost 14 years
    ill-formed means it didn't compile(edit, meant to say didn't compile). The above code will compile, it's just leaky.
  • Prasoon Saurav
    Prasoon Saurav almost 14 years
    @Igor: I never said that the code won't compile. I have explicitly mentioned that the code has memory leak.
  • Igor Zevaka
    Igor Zevaka almost 14 years
    You said ill-formed, which means not well-formed, which, from the spec, means a C++ program constructed according to the syntax rules, diagnosable semantic rules, and the One Definition Rule - i.e. doesn't compile.
  • Prasoon Saurav
    Prasoon Saurav almost 14 years
    @Igor: Thanks for reminding that. Changed ill formed to ` leaks memory.`
  • Georg Fritzsche
    Georg Fritzsche almost 14 years
    "where a programmer must pick which base class constructor" - this is only the case if there is no accessible default constructor.
  • Igor Zevaka
    Igor Zevaka almost 14 years
    Correct, let me clarify that.