1
votes

To help better understand my question, I'm referring to a topic discussed in the book "The C++ Programming Language 4th edition" chapter 27 section 2.1.

The author is talking about the danger of polymorphic types and built-in arrays. He gives the following example:

void maul(Shape∗ p, int n) // Danger!
{
    for (int i=0; i!=n; ++i)
        p[i].draw(); //looks innocent; it is not
}

void user()
{
    Circle image[10]; // an image is composed of 10 Circles
    // ...
    maul(image,10); // ‘‘maul’’ 10 Circles
    // ...
}

We're told that Shape is an abstract size of size 4 and Circle inherit Shape and add an extra 2 members, center and radius which adds to the type size, hence, sizeof(Circle)>sizeof(Shape). Now the author explains that given the following view for example:

user() view: image[0] image[1] image[2] image[3]<br/>
maul() view: p[0] p[1] p[2] p[3]

The call of p[1].draw() (emphasis on p[1], for p[0] it will call the right function) will fail because there's no virtual function pointer where it's expected.

Now I know how virtual function tables works, but I don't understand how the size of the type or its layout affect a virtual function call? When the compiler see a call to a virtual function doesn't it replace it with something similar to:

p[1]._vfptr->draw_impl();

Assuming I'm right, how is the size of the derived object/its layout broke the call for it.

1
How do you expect to do a virtual function call when you don't even have the correct addresses of the Circle objects?Brian Bi
Do you understand that image + 1 != p + 1?Jarod42
@CoryKramer: I disagree with duplicated, here p[1] is not even a correct Shape.Jarod42
polymorphism-pointers-to-arrays seems a better duplicate.Jarod42
Yeah, duplicate, though I would rather see a fresh answer with a simple explanation.. "a pointer to the next base class subobject of the next member of the derived class array." - let's just say, it's not beginner friendly...Karoly Horvath

1 Answers

0
votes

When i is 0, this works:

p[i].draw(); // Shape* p

p[0] is the same as *(p+0) which is just *p, and calling a function through a base class pointer is exactly what virtual functions are designed to support.

But what about when i is 1? Now you have p[1] which is *(p+1). And what is p+1, well, it's the address of the "next" object after p. But array indexing is a concept from C, where there are no virtual tables or derived classes, so the indexing is done by adding the compile-time sizeof(*p) which is sizeof(Shape). You end up with something like:

((Shape*)((char*)p + sizeof(Shape)))->draw();

That is, increment p (the beginning of the array of Circles) by sizeof(Shape) bytes, then dereference it to call draw(). But this indexing is wrong, because it should be sizeof(Circle) bytes. And C, which gives us the array type, does not give us any tools to deal directly with this, because it did not foresee C++.

Note that C++11 std::array<> won't help, other than making the above a compile-time error (which I suppose is much better than the runtime undefined behavior you get with the C-style array). And good old std::vector<> is the same. You can store an array of pointers, in which case they should usually be smart pointers like std::shared_ptr. That works because pointers to any type (apart from pointers to member functions!) are the same size, so indexing through an array of them works fine.