3

I'm trying to figure out the tricks of class inheritance in C++ and I've built a sample project:

#include "stdafx.h"
#include <iostream>
using namespace std;

class A
{
public:
    A()
    {
        cout << "Class A initialized" << endl;
    }

    ~A()
    {
        cout << "Class A destructed" << endl;
    }
};

class B : public A
{
public:
    B()
    {
        cout << "Class B initialized" << endl;
    }

    ~B()
    {
        cout << "Class B destructed" << endl;
    }
};


int _tmain(int argc, _TCHAR* argv[])
{
    cout << "A* a = new A()" << endl;
    A* a = new A();
    cout << "B* b = new B ()" << endl;
    B* b = new B ();
    cout << "A* ab = new B()" << endl;
    A* ab = new B();

    cout << "delete a" << endl;
    delete a;
    cout << "delete b" << endl;
    delete b;
    cout << "delete ab" << endl;
    delete ab;

    int i;
    cin >> i;

    return 0;
}

The output I get is:

A* a = new A()
Class A initialized
B* b = new B ()
Class A initialized
Class B initialized
A* ab = new B()
Class A initialized
Class B initialized
delete a
Class A destructed
delete b
Class B destructed
Class A destructed
delete ab
Class A destructed

I can understand the behavior of class B as a derived class - first it constructs the base class and then the derived class. When it calls the destructor, it does the work the other way around. Seems logical.

What I can't understand, is the behavior of ab (allocation of B which I put into an A pointer), why does the constructor act the same as pure B, but the destructor runs only on A?

Thanks.

Max Mor
  • 96
  • 1
  • 6

2 Answers2

2

The compiler calls member functions of the class that correspond to the static type of the pointer. The type of pointer ab is A * so the compiler calls the destructor of class A. If you would declare the destructor as virtual as for example

class A
{
public:
    //...
    virtual ~A()
    {
        cout << "Class A destructed" << endl;
    }
};

then the compiler would use the table of vitual function pointers. In this case that is in the case of deleting ab the table would contain the pointer that refers to the destructor of the derived class.

As for the constructor then when you use operator new B() then the static type used in the expression is B. So the consttructor of B is called along with the constructor of A as the constructor of the base class.

Vlad from Moscow
  • 301,070
  • 26
  • 186
  • 335
  • This answer is actually incorrect; destructors obey slightly different rules, and in fact, his program has undefined behavior. – James Kanze Apr 08 '14 at 21:24
  • On rereading it: this answer is actually wrong (as is any answer which doesn't point out the undefined behavior). – James Kanze Apr 08 '14 at 21:28
  • @James Kanze Could you elaborate what is wrong with my answer? – Vlad from Moscow Apr 08 '14 at 21:36
  • Practically everything, except that the destructor should be virtual. It doesn't describe at all what happens, either in theory or in practice. – James Kanze Apr 08 '14 at 21:52
  • @James Kanze Read the question one more. The question is "why does the constructor act the same as pure B, but the destructor runs only on A?" So my answer is totally correct. It explains why there is called the constructor of B and at the same time the destructor of A. It is you who did not understand the question. We are not discussing here the undefined behaviour. We are discussing why the constructor of B and destructor of A were called. – Vlad from Moscow Apr 08 '14 at 21:58
  • And the answer as to why the destructor runs only on A is because his code contains undefined behavior. Any other answer is incorrect. – James Kanze Apr 09 '14 at 08:06
  • @James Kanze It is totally wrong. It is a defined behaviour that the destructor of class A is called. One more it is a defined behaviour by the C++ Standard. You even do not understand what is the undefined behaviour here. The undefined behaviour here is that the destructor of the derived class is not called and nothing more. – Vlad from Moscow Apr 09 '14 at 08:52
  • 1
    No. It is undefined behavior. That's why I said that your answer is wrong. See §5.3.5/3: "[...]if the static type of the object to be deleted is different from its dynamic type, the static type shall be a base class of the dynamic type of the object to be deleted and the static type shall have a virtual destructor or the behavior is undefined." It's not unusual for the program to crash in such cases, or for memory to be corrupted, causing a crash later. – James Kanze Apr 09 '14 at 09:00
  • @James Kanze One again you do not underastand what undefined behaviour here is talking about. Calling the base class destructor is very well defined behaviour. The undefined behaviour in this case means only that the derived class object will not be correctly destroyed and deleted. – Vlad from Moscow Apr 09 '14 at 11:06
  • That's _not_ what the standard says. The standard says that the behavior is undefined, period. Just what don't you understand about undefined. (It's also not always the case that only the base class destructor will be called; I've seen system crashes and corrupted memory from such cases as well.) – James Kanze Apr 09 '14 at 12:04
  • @James Kanze The Standard clear answers what destructor will be called. There is no any undefined behaviour. The undefined behaviour occurs after the destructor of the base class is called. – Vlad from Moscow Apr 09 '14 at 12:10
  • 1
    Which standard? I quoted C++11, directly, where is says that this is undefined behavior. The text I quoted was identical in earlier versions of the standard. The text I quoted is clear and precise; I don't see what you don't understand about it. – James Kanze Apr 09 '14 at 12:23
  • @James Kanze It seems that you do not understand what you read. The answer o the question what constructor will be called is well defined in the standard. There is no any undefined behaviour. All compilers I am repeating all compilers will call the destructor of the base class because this behaviour is well-defined in the standard. So your answer is irrelevant relative to the question of the author of the thread. Moreover you have some problems with understanding of the Standard. – Vlad from Moscow Apr 09 '14 at 12:34
  • I give up. The standard clearly says "undefined behavior", and I quoted the passage. If you don't understand undefined behavior, there's not much I can do about it. I've also seen crashes and corrupted memory using Sun CC, g++ and VC++, which clearly contradicts your statement about what all compilers do, so that statement of yours is clearly false. – James Kanze Apr 09 '14 at 13:34
  • @James Kanze As I said you even do not understand what you read. The undefined behaviour is a result of calling only the base class destructor for an object of the derived class. But the standard clearly says what destrauctor will be called in this case. So the fact that the destructor of base class will be called is well defined in the standard. – Vlad from Moscow Apr 09 '14 at 13:58
  • Just what don't you understand about "undefined behavior". The standard says it is undefined behavior. The standard says that "undefined behavior" is "behavior for which this International Standard imposes no requirements". Anything can (and in this case, does) happen. Where do you find anything in the standard which says otherwise? – James Kanze Apr 09 '14 at 14:06
  • 1
    @James Kanze The standard without any doubts says that in this case the destructor of the base class will be called. It is the consequence of such a call has undefined behaviour. It is different things. And the author of the thread asked why will be called the base class destructor and not the derived class destructor. So it is all clear except you because you even do not understand what you read. – Vlad from Moscow Apr 09 '14 at 14:36
  • Just what don't you understand about "undefined"? You continue to say that the standard says so and so, but you don't cite anything in the standard to back this up. The passage I quote clearly says "undefined behavior", and the standard defines undefined behavior as meaning that there are _no_ constraints on what happens. – James Kanze Apr 10 '14 at 08:19
  • @James Kanze The question is why was the destructor of the base class called. It has nothing common with the undefined behaviour. It is clear enough. If you do not understand the question then reread it as many times as you need. – Vlad from Moscow Apr 10 '14 at 09:00
  • The reason the base class destructor is called is because the program has undefined behavior, so anything could happen. There's no guarantee that the base class destructor will be called. If you don't understand C++, I'd suggest you find a good course. – James Kanze Apr 10 '14 at 10:05
  • @James Kanze It is your serious mistake. There is nothing undefined that all compilers I am repeating all compilers insert code that calls the base class constructor. It is the type of the pointer that defines the set of functions that may be called. This rule is applied to all member functions of a class and the destructor is not an exception. The undefined behaviour takes place after the call of the destructor because the state of the object pointed to by the pointer is undefined. – Vlad from Moscow Apr 10 '14 at 11:21
  • You can repeat all you like, but what you are saying is simply false. For starters, at least VC++, g++ and Sun CC will sometimes crash or corrupt memory in this case---so your statement about all compilers is wrong. And you have yet to cite any code in the standard which contradicts the paragraphs I've cited. – James Kanze Apr 10 '14 at 11:25
  • @James Kanze You are speacking about the consequence of the call but the question is about why was the base class constructor called. One more it is different things. There is nothing common with the undefined behaviour. Because there is no any behaviour. The code of calling the base clss constructor is generated at compile time. The program is not run yet. The compiler has to define what function it has to call and the Standard clear says what function in this case will be clled. – Vlad from Moscow Apr 10 '14 at 11:36
  • I am speaking about what the standard says, in the passage I quoted. There's no mention of calling the base class destructor or whatever. It says simply: if the dynamic type is different, and the destructor of the static type is not virtual, the behavior is undefined. Period. That's all there is to it. – James Kanze Apr 10 '14 at 12:30
1

There is a fundamental difference between constructors and destructors (or constructors and any other function, for that matter): when constructing an object, you must specify its exact type, in the source code. For all other functions (including the destructor), it is possible to only mention a base, provided certain conditions are met. One of those conditions is that the function (or the destructor) be virtual in the base class.

In the case of destructors, there is an additional constraint, because the destructor is involved in a delete, which in turn requires the address of the complete object in order to free the memory correctly. Thus, given A* pA;, an expression like pA->f() will call the function f in the base class if it is not virtual, but the function f() in the derive class if it is virtual, and the derived class overrides it. On the other hand, delete pA; will call the destructor of the derived class if the destructor in the base is virtual, but is undefined behavior if pA points to a derived class, and the destructor in the base is not virtual. There is no question of just 9alling the destructor of the base class; although this might be the actual behavior in simple cases, the behavior is undefined in all cases.

For this reason, it has often been recommended that if a class is designed to be used as a base class, the destructor should be either virtual, or protected. IMHO, it depends on the class: if anyone misundertands std::exception<> to the point of writing something like:

std::exception<...>* pIter = new std::vector<...>::iterator;
//  ...
delete pIter;

there's no hope, and it's not worth the bother of defining a destructor for std::iterator, just to make it protected (and in pre-C++11, making it impossible that the derived iterator be a POD).

Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
James Kanze
  • 150,581
  • 18
  • 184
  • 329