35
votes

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!

7
Not exactly what you want but it shows what the compiler generated destructor will do. stackoverflow.com/questions/1810163/…Martin York

7 Answers

17
votes

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).
 }
16
votes

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.

11
votes

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.

2
votes

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.

2
votes

As Igor says constructors must be called for base classes. Consider what would happen if it wouldn't be called:

struct A {
    std::string s;
    virtual ~A() {}
};

struct B : A {};

If the destructor for A would not be called when deleting a B instance, A would never be cleaned up.

2
votes

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.

0
votes

When any object is destroyed, destructors run for all sub-objects. This includes both reuse by containment and reuse by inheritance.