In C++ Inheritance, Derived class destructor not called when pointer object to base class is pointed to array of derived class

In C++ Inheritance, Derived class destructor not called when pointer object to base class is pointed to array of derived class

17

I have an Animal class with constructor and destructor.
Cat have a private Brain* attribute.
Upon construction, Cat creates his Brain using new Brain();
Upon destruction, Cat deletes his Brain.
I don’t understand why The cat’s and brain’s destructors not called, when my Base class destructor is virtual?

#include <iostream>

using std::cout ;
using std::endl ;

class Brain {
public:
    Brain (void){cout << "Brain constructor" << endl ;}
    ~Brain (void){cout << "Brain destructor" << endl ;}
} ;

class Animal
{
public:
    Animal (void){cout << "Animal constructor" << endl ;}
    virtual ~Animal (void){cout << "Animal destructor" << endl ;}
} ;

class Cat : public Animal
{
public:
    Cat (void){
                cout << "Cat constructor" << endl ;
        myPointer = new Brain() ;
    }
    ~Cat (void){
        cout << "Cat destructor" << endl ;
        delete myPointer ;
    }
private:
    Brain* myPointer ;
} ;

int main()
{
    const Animal* j = new Cat[1] ;
    delete [] j ;
}

Gives output

Animal constructor
Cat constructor
Brain constructor
Animal destructor

Share
Improve this question

New contributor

suhovhan is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.

12

  • 5

    Totally OT, but if a function doesn't have any arguments then you don't need to write anything. Instead of e.g. Cat(void) only Cat() will work.

    – Some programmer dude

    yesterday

  • 1

    @463035818_is_not_a_number clang 14.0.3 on macOS reproduces OP's output for me.

    – Botje

    yesterday

  • 1

    @463035818_is_not_a_number I changed your first compiler explorer link to x86-64 clang 16.0.0 and got OP's output.

    – Botje

    yesterday

  • 1

    Minimal repro: godbolt.org/z/EaaYnKPMP Note the const isn't needed, but array new/delete is.

    – Mike Vine

    yesterday

  • 2

    (I've answered this, but interesting to note that GCC gets just as confused later if you change the number of cats allocated to 2)

    – Mike Vine

    23 hours ago

3 Answers
3

Reset to default

Highest score (default)

Trending (recent votes count more)

Date modified (newest first)

Date created (oldest first)

18

Note that whilst a Cat is an Animal, an array of Cats is not an array of Animals. In other words, arrays are invariant in C++, not covariant like they are in some other languages.

So you are up-casting this array and this later confuses the compiler. You must do array delete[] in this case on the correct, original, type – Cat*.

Note that you would have similar issues for the same reason if you allocated an array of 2 or more Cats, cast this to an Animal* and then tried to use the second or subsequent Animal.

Share
Improve this answer

6

  • 3

    i will never stop learning new quirks about c-arrays 🙂

    – 463035818_is_not_a_number

    yesterday

  • 1

    Typo: down casting -> Upcasting (Downcasting would be Animal -> Cat)

    – Botje

    yesterday

  • 2

    @463035818_is_not_a_number indeed – I had never realised this and had to work it out from first principles.

    – Mike Vine

    yesterday

  • 1

    std::vector to the rescue

    – 463035818_is_not_a_number

    yesterday

  • Does a tool like CPPCheck see the issue? Can someone point a precise wording, perhaps in cppreference, about why, while storing a derived * as base * is legal, the unexpected delete [] is called?

    – Dharmesh946

    23 hours ago

4

I answer to my own comment:
https://en.cppreference.com/w/cpp/language/delete
The pointed-to type of expression must be similar to the element type of the array object
https://en.cppreference.com/w/cpp/language/reinterpret_cast#Type_aliasing

Informally, two types are similar if, ignoring top-level
cv-qualification:

  • they are the same type; or
  • they are both pointers, and the pointed-to types are similar;or
  • they are both pointers to member of the same
    class, and the types of the pointed-to members are similar; or
  • they are both arrays of the same size or both arrays of unknown bound, and
    the array element types are similar. (until C++20)
  • they are both arrays of the same size or at least one of them is array of unknown
    bound, and the array element types are similar.

So far as I understand, inheritance is not similarity…

Share
Improve this answer

1

  • Inheritance cannot be similarity indeed. Note that the array contains values, not references, and a Cat values is larger than an Animal value, so it won't fit there. That makes the cast from Cat * to Animal * invalid when pointing to an array, but since the type does not indicate it's pointing to an array and for single instance it is valid, the programmer has to mind this.

    – Jan Hudec

    5 hours ago

3

It’s Undefined Behaviour in my understanding because of (in 7.6.2.9 Delete,p2, Emphasis mine):

In a single-object delete expression, the value of the operand of
delete may be a null pointer value, a pointer value that resulted from
a previous non-array new-expression, or a pointer to a base class
subobject of an object created by such a new-expression
. If not, the
behavior is undefined. In an array delete expression, the value of
the operand of delete may be a null pointer value or a pointer
value that resulted from a previous array new-expression

Which basically means that for delete[] the type must be the exact one from new[] (no base class sub-objects allowed like delete).

So for the reason that is like this – in my opinion this time is obvious – the implementation needs to know how much is the full object size so it can iterate to the next array element.

Counter argument is though because currently the implementation needs to store the number of array elements anyway (so it knows how many to deconstruct) – it might also store the full type/size as well.

Also this is the case for exceptions where Polymorphic catch matching is performed under the hood (and is also mandated by the standard).

Share
Improve this answer

2

  • 1

    This encourages dangerous behaviour though. If you allow this then you are basically saying Animal* a = new Cat[2]; delete[] a; is ok. This would implicitly imply that a is useful on its own when it very much is not. a[1] is undefined behaviour at this point as array index uses the type (a[i] is defined as (a+i)) which moves on a by sizeof(Animal) bytes and not sizeof(Cat) bytes which it must do. So just avoiding this whole issue by not allowing a to be useful across the board is probably the right thing to do.

    – Mike Vine

    55 mins ago

  • @MikeVine I didn't thought of that. Good point I guess.

    – AnArrayOfFunctions

    14 mins ago

Your Answer

suhovhan is a new contributor. Be nice, and check out our Code of Conduct.

Draft saved
Draft discarded

Post as a guest

Required, but never shown


By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged

or ask your own question.

Leave a Reply

Your email address will not be published. Required fields are marked *