1
votes

Well so i have been trying to understand OOP concepts through C++ , however i am not able to get some parts of virtual destructors.

I have written a small snippet :

class A{
    int x;
public: 
    virtual void show(){
        cout << " In A\n"; 
    }
    virtual ~A(){
        cout << "~A\n";
    };
};

class B: public A{
    int y;
public: 
    virtual void show(){
        cout << " In B\n"; 
    }
    virtual ~B(){
        cout << "~B\n";
    };
};

class C: public A{
    int z;
public: 
    virtual void show(){
        cout << " In C\n"; 
    }
    virtual ~C(){
        cout << "~C\n";
    };
};
class E: public A{
    int z;
public: 
    virtual void show(){
        cout << " In E\n"; 
    }
    virtual ~E(){
        cout << "~E\n";
    };
};

class D: public B , public C , public E{
    int z1;
public: 
    virtual void show(){
        cout << " In D\n"; 
    }
    virtual ~D(){
        cout << "~D\n";
    };
};

signed main(){
    // A * a = new A();
    // B *b = new B();
    D *d = new D();
    B *b = d;
    C *c = d;
    E * e = d;
    A * a = new A();
    cout << d << "\n";
    cout << b  << "\n";
    cout  << c << "\n";
    cout << e << "\n";
    delete b;
    // a -> show();

}

On running the code , i get the result as :

0x7f8c5e500000
0x7f8c5e500000
0x7f8c5e500018
0x7f8c5e500030
~D
~E
~A
~C
~A
~B
~A

Now three questions :

  • According to the wikipedia article , virtual_table , it was referred that object c gets an address +8 bytes than that of d and b , what happens in case of e.
  • When i call delete b instead of delete d , also get the same order sequence of virtual destructors , so why is the derived class destructor called
  • The virtual destructors are called only when i delete an object , then how are the vtable and vpointers gets deleted when the program ends ( when i run the code without the delete d the execution just stops without printing anything ).
3
Is that about virtual inheritance?curiousguy

3 Answers

2
votes

Your questions in order:

(1) Yes, pointers to bases refering to objects of derived classes with multiple inheritance may change their numerical value compared to a pointer to the most derived type. The reason is that the base class is a part of the derived class, much like a member, residing at an offset. Only for the first derived class in multi-inheritance this offset can be 0. This is the reason why such pointers cannot be cast with a simple reinterpret_cast().

(2) b points to an E which also is-an A.

Exactly that is what being virtual means for a member function: The code generated by the compiler inspects the object pointed to at run time and calls the function defined for the actual type of the object (which is an E), as opposed to the type of the expression used to access that object (which is B). The type of the expression is fully determined at compile time; the type of the actual complete object is not.

If you do not declare a destructor virtual the program may behave as you perhaps expected: The compiler will create code which simply calls the function defined for the type of the expression (for B), without any run-time look-ups. Non-virtual member function calls are slightly more efficient; but in the case of destructors as in your case the behavior is undefined when destroying through a base class expression. If your destructor is public it should be virtual because this scenario could happen.

Herb Sutter has written an article about virtual functions including virtual destructors that's worth reading.

(3) The memory, including dynamically allocated memory, is released and made available again for other uses by modern standard operating systems when the program has exited. (This may not be the case in old operating systems or freestanding implementations, if they offer dynamic allocation.) Destructors of dynamically allocated objects, however, will not be called, which may be a problem if they hold resources like database or network connections which should better be released.

0
votes

Regarding the addresses of the objects. As already explained in another answer this is compiler dependent. However it can still be explained.

Address of objects in Multiple Inheritance (a possible compiler implementation)

Here is a possible memory diagram, assuming that the pointer to the virtual table is 8 bytes and int is 4 bytes.

enter image description here

Class D first has its pointer to virtual table (vtbl_ptr or vptr) then comes class B without its own vtbl_ptr, as it can share the same vtbl as D.

Classes C and E must come with their own embedded vtbl_ptr. It will point to the vtbl of D (almost..., there is a thunk issue to handle but let's ignore it, you can read about thunk in the links below but this doesn't affect the need for additional vtbl_ptr).

The additional vptr for each additional base class is required so when we look at C or E, the position of the vptr is always at the same location, i.e. at the top of the object, regardless if it is actually a concrete C or it is a D that is held as C. And the same for E and any other base class that is not the first inherited base.

The addresses that we may see according to the above:

D d; // sitting at some address X
B* b = &d; // same address
C* c = &d; // jumps over vtbl_ptr (8 bytes) + B without vtbl_ptr (8 bytes)
           // thus X + 16 -- or X + 10 in hexa
E* e = &d; // jumps in addition over C part including vtbl_ptr (16 bytes)
           // thus X + 32 -- or X + 20 in hexa

Note that the math for the addresses that appear in the question might be a bit different, as said things are compiler dependent. Size of int may be different, padding might be different and the way to arrange the vtbl and vptr is also compiler dependent.


To read more about object layout and address calulations, see:

And the following SO entries on the subject:

0
votes

According to the wikipedia article , virtual_table , it was referred that object c gets an address +8 bytes than that of d and b , what happens in case of e.

Addresses are often compiler-dependent, and hence pretty dicey. I wouldn't rely on them being any particular value.

When i call delete b instead of delete d , also get the same order sequence of virtual destructors , so why is the derived class destructor called

The type of the pointer doesn't matter. The underlying object was created with new D() so those are the destructors that get called. This is because it might be difficult to delete objects properly otherwise -- if you have a factory that creates various subclasses, how would you know which type to delete it as?

(What's actually going on here is that (pointers to) the destructors are stored in the object's vtable.)

The virtual destructors are called only when i delete an object , then how are the vtable and vpointers gets deleted when the program ends ( when i run the code without the delete d the execution just stops without printing anything ).

If you never delete something, it never gets cleaned up. The program ends without freeing that memory from the heap. This is a "memory leak". When the program ends, the OS cleans up the whole program's heap in one go (without caring what's in it).