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
12
3 Answers
3
Reset to default
Highest score (default)
Trending (recent votes count more)
Date modified (newest first)
Date created (oldest first)
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.
6
-
3
i will never stop learning new quirks about c-arrays 🙂
– 463035818_is_not_a_numberyesterday
-
1
Typo: down casting -> Upcasting (Downcasting would be Animal -> Cat)
– Botjeyesterday
-
2
@463035818_is_not_a_number indeed – I had never realised this and had to work it out from first principles.
– Mike Vineyesterday
-
1
std::vector
to the rescue– 463035818_is_not_a_numberyesterday
-
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?
– Dharmesh94623 hours ago
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…
1
-
Inheritance cannot be similarity indeed. Note that the array contains values, not references, and a
Cat
values is larger than anAnimal
value, so it won't fit there. That makes the cast fromCat *
toAnimal *
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 Hudec5 hours ago
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).
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 thata
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 ona
bysizeof(Animal)
bytes and notsizeof(Cat)
bytes which it must do. So just avoiding this whole issue by not allowinga
to be useful across the board is probably the right thing to do.– Mike Vine55 mins ago
-
@MikeVine I didn't thought of that. Good point I guess.
– AnArrayOfFunctions14 mins ago
Your Answer
suhovhan is a new contributor. Be nice, and check out our Code of Conduct.
Sign up or log in
Post as a guest
Required, but never shown
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.
or ask your own question.
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)
onlyCat()
will work.yesterday
@463035818_is_not_a_number clang 14.0.3 on macOS reproduces OP's output for me.
yesterday
@463035818_is_not_a_number I changed your first compiler explorer link to x86-64 clang 16.0.0 and got OP's output.
yesterday
Minimal repro: godbolt.org/z/EaaYnKPMP Note the const isn't needed, but array new/delete is.
yesterday
(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)
23 hours ago
|
Show 7 more comments