Reputation: 531
I have an Animal
class with a virtual destructor, and a derived class Cat
.
#include <iostream>
struct Animal
{
Animal() { std::cout << "Animal constructor" << std::endl; }
virtual ~Animal() { std::cout << "Animal destructor" << std::endl; }
};
struct Cat : public Animal
{
Cat() { std::cout << "Cat constructor" << std::endl; }
~Cat() override { std::cout << "Cat destructor" << std::endl; }
};
int main()
{
const Animal *j = new Cat[1];
delete[] j;
}
This gives the output:
Animal constructor
Cat constructor
Animal destructor
I don't understand why is the Cat
's destructor not called, when my base class destructor is virtual?
Upvotes: 43
Views: 4194
Reputation: 1
The Cat's destructor is not called because the pointer J is declared as pointer to a constant Animal. When you delete an array of objects through a pointer to a base class with a virtual destructor the destructor of each derived class object is not called unless the pointer type is of derived class.
So for calling the Cat Destructor. modity the pointer type to
int main()
{
const Cat* j=new Cat[1];
delete[] j;
}
Upvotes: 0
Reputation: 71
My only criticism of the other answers is that they are too tame. Arrays work because the entries all have the same size, so the starting address of the entry with index 5 can be computed easily. Casting array of Derived to array of Base (which is what you are doing here) could have much worse consequences than you have shown. (If the array happens to have only one entry or Derived has no new data members you might be OK.) A[1].method() will probably do something strange because (Base*)(A)+1 is not even the starting address of any object. The method will read the wrong data, and the effect of any change will be highly unpredictable. Any call to a virtual function will use an entirely meaningless vtable pointer, so who knows what 'code' will be executed. Even if that call is not an immediate disaster, you might override a vtable pointer. Destruction of the array will almost certainly delete some meaningless 'pointers'.
Pointer to single object (allocated & deleted without '[]') is fine. You can cast to Base*, dynamic_cast back, and call virtual functions. That's why Ayxan Haqverdili's suggestion works. Array of X should never have anything except objects of run-time type X, and new[] (even new[1]) creates arrays.
Upvotes: 7
Reputation: 3
Make the Cat destructor also virtual and the code works as expected.
Upvotes: -3
Reputation: 3754
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.
I wrote counter arguments for why this could be different - but after some thought (and reading the comments) - I realised that such a solution is solving X-Y problem.
Upvotes: 10
Reputation: 29985
Others have explained the issue, but if you want an array of polymorphic objects, you want an array of pointers. Here's one way:
vector<unique_ptr<Animal>> animals;
animals.push_back(std::make_unique<Cat>());
animals.push_back(std::make_unique<Dog>());
animals.push_back(std::make_unique<Monkey>());
Upvotes: 3
Reputation: 2714
I answer to my own comment: https://en.cppreference.com/w/cpp/language/delete (emphasis mine)
For the second (array) form, expression must be a null pointer value or a pointer value previously obtained by an array form of new-expression whose allocation function was not a non-allocating form (i.e. overload (10)). The pointed-to type of expression must be similar to the element type of the array object. If expression is anything else, including if it's a pointer obtained by the non-array form of new-expression, the behavior is undefined.
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...
Upvotes: 9
Reputation: 9837
Note that whilst a Cat
is an Animal
, an array of Cat
s is not an array of Animal
s. 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 Cat
s, cast this to an Animal*
and then tried to use the second or subsequent Animal.
Upvotes: 42