16
#include <iostream>

using namespace std;

struct Base
{
    virtual ~Base()
    {
        cout << "~Base(): " << b << endl;
    }

    int b = 1;
};

struct Derived : Base
{
    ~Derived() override
    {
        cout << "~Derived(): " << d << endl;
    }

    int d = 2;
};

int main()
{
    Base* p = new Derived[4];
    delete[] p;
}

The output is as follws: (Visual Studio 2015 with Clang 3.8)

~Base(): 1
~Base(): 2
~Base(): -2071674928
~Base(): 1

Why does polymorphism not apply on arrays in C++?

xmllmx
  • 39,765
  • 26
  • 162
  • 323
  • 9
    Polymorphism only works through pointers and the elements of the array are not pointers, they are value types. You would need an array of pointers and to delete them each individually. (or a vector of smart pointers) – Galik Dec 13 '16 at 04:02
  • 2
    i agree with @Galik comment, but it is working fine for me, proper polymorphic behavior. sh-4.2$ g++ -std=c++11 -o main *.cpp ~Derived(): 2~Base(): 1 ~Derived(): 2~Base(): 1~Derived(): 2~Base(): 1 ~Derived(): 2~Base(): 1 – instance Dec 13 '16 at 04:15
  • This won't work as arrays uses pointer aritmetic to access array els which in your case is sizeof(Base) when on the other hand one element in Your array is sizeof(Derived) in consequence when accessing array el You will endup in wrong address. – Mateusz Wojtczak Dec 13 '16 at 04:16
  • Checked this same code here :- https://www.tutorialspoint.com/compile_cpp11_online.php ...... working fine? Why? – instance Dec 13 '16 at 04:18
  • This code makes no sense. Think about `sizeof(*p)`. – David Schwartz Dec 13 '16 at 08:40

4 Answers4

21

Given,

Base* p = Derived[4];

the C++11 Standard makes

delete [] p;

to be undefined behavior.

5.3.5 Delete

...

2 ... In the second alternative (delete array) if the dynamic type of the object to be deleted differs from its static type, the behavior is undefined.

From a memory layout point of view, it also makes sense why delete [] p; will result in undefined behavior.

If sizeof(Derived) is N, new Derived[4] allocates memory that will be something like:

+--------+--------+--------+--------+
|   N    |   N    |   N    |   N    |
+--------+--------+--------+--------+

In general, sizeof(Base) <= sizeof(Derived). In your case, sizeof(Base) < sizeof(Derived) since Derived has an additional member variable.

When you use:

Base* p = new Derived[4];

you have:

p
|
V
+--------+--------+--------+--------+
|   N    |   N    |   N    |   N    |
+--------+--------+--------+--------+

p+1 points to someplace in the middle of the first object since sizeof(Base) < sizeof(Derived).

       p+1
       |
       V
+--------+--------+--------+--------+
|   N    |   N    |   N    |   N    |
+--------+--------+--------+--------+

When the destructor is called on p+1, the pointer does not point to the start of an object. Hence, the program exhibits symptoms of undefined behavior.


A Related Problem

Due to the differences in sizes of Base and Derived, you cannot iterate over the elements of the dynamically allocated array using p.

for ( int i = 0; i < 4; ++i )
{
   // Do something with p[i]
   // will not work since p+i does not necessary point to an object
   // boundary.
}
R Sahu
  • 204,454
  • 14
  • 159
  • 270
15

You get undefined behavior because operator delete[] has no idea what kind of object is stored in the array, so it trusts the static type to decide on offsets of individual objects. The standard says the following:

In the second alternative (delete array) if the dynamic type of the object to be deleted differs from its static type, the behavior is undefined.

The array's static type needs to match the element type that you use for the allocation:

Derived* p = new Derived[4]; // Obviously, this works

This Q&A goes into more details on the reason why the standard has this requirement.

As far as fixing this behavior, you need to create an array of pointers, regular or smart (preferably smart to simplify memory management).

Community
  • 1
  • 1
Sergey Kalinichenko
  • 714,442
  • 84
  • 1,110
  • 1,523
  • 1
    It's actually a "delete array expression" that relies on the static type and dynamic type being the same. `operator delete[]` is something else (albeit a something else that is used by a delete expression). – Peter Dec 13 '16 at 04:28
  • 1
    Or a vector of smart pointers to simplify it even more. – user253751 Dec 13 '16 at 06:29
4

It has to do with the concepts of covariance and contravariance. I'll give an example that should hopefully clarify things.

We'll start with a simple hierarchy. Suppose we're dealing with creating and destroying objects in the real world. We have 3D objects (such as wooden blocks) and 2D objects (such as sheets of paper). A 2D object can be treated like a 3D object, except with negligible height. This is the proper subclassing direction, since the reverse is not true; 2D objects can be laid flat on top of each other, something which is very difficult at best to do with arbitrary 3D objects.

Something that produces objects, like a printer, is covariant. Suppose your friend wants to borrow a 3D printer, take ten objects that it produces, and stick them in a box. You can give them a 2D printer; it will print ten pages, and your friend will stick them in a box. However, if that friend wanted to take ten 2D objects and stick them in a folder, you couldn't give them a 3D printer.

Something that consumes objects, like a shredder, is contravariant. Your friend has the folder with the pages it printed, but it didn't sell, so he wants to shred it. You can give them a 3D shredder, like the industrial ones used for shredding things like cars, and it will work just fine; feeding it pages causes it no hardship at all. On the other hand, if he wanted to shred the box of objects he got earlier, you couldn't give him the 2D shredder, as the objects may not fit into the slot.

Arrays are invariant; they both consume objects (with assignment) and produce objects (with array access). As an example of this kind of relation, take a fax machine of some kind. If your friend wants a fax machine, they need the exact kind they're asking for; they can't have a super- or subclass, as you can't stick 3D objects into a slot for 2D paper, and you can't bind objects produced by a 3D fax machine into a book.

Zemyla
  • 468
  • 3
  • 7
2

While the other two answers (here and here) have approached the problem from the technical side and very well explained why the compiler would have an impossible task trying to compile the code that you have given it, they did not explain the conceptual problem with your question.

Polymorphism can only work when we are talking about Class-Subclass relationship. So when we have the situation as you have coded we have:

enter image description here

We are saying "All instances of the Derived are also instances of Base". Note that this must hold or we cannot even begin to talk about polymorphism. In this case it holds and thus we can use the pointer to Derived where the code expects a pointer to Base.

But then you are attempting to do something different:

enter image description here

And here we have a problem. While in set theory we could say that a set of a subclass is also a set of a superclass, it is not true in programming. The problem is somewhat increased by the fact that the difference is "only two characters". But the array of elements is in a way a completely different thing then any one of those elements.

Perhaps if you rewrote the code using std::array template that would become more clear:

#include <iostream>
#include <array>

struct Base
{   
    virtual ~Base()
    {
        std::cout << "~Base()" << std::endl;
    }
};

struct Derived : Base
{   
    ~Derived() override
    {
        std::cout << "~Derived()" << std::endl;
    }
};

int main()
{
    std::array<Base, 4>* p = new std::array<Derived, 4>;
    delete[] p;
}

This code can clearly not compile, templates are do not become subclasses of one another depending upon their parameters. In a way this would be like expecting a pointer to one class to become a pointer to a completely unrelated class, this will simply not work.

Hopefully this will help some people who want a more intuitive understanding of what is happening, rather than a technical explanation.

Community
  • 1
  • 1
v010dya
  • 5,296
  • 7
  • 28
  • 48