1

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.

JoJo Dmlx
  • 111
  • 1
  • 6
  • 3
    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 Nov 06 '15 at 18:33
  • Do you understand that `image + 1 != p + 1`? – Jarod42 Nov 06 '15 at 18:33
  • Thanks for explaining, I've read about slicing and it made it clear for me. – JoJo Dmlx Nov 06 '15 at 18:39
  • 1
    @CoryKramer: I disagree with duplicated, here `p[1]` is not even a correct `Shape`. – Jarod42 Nov 06 '15 at 18:44
  • 1
    [polymorphism-pointers-to-arrays](http://stackoverflow.com/questions/1411844/polymorphism-pointers-to-arrays) seems a better duplicate. – Jarod42 Nov 06 '15 at 18:50
  • @Jarod42 yea this is exactly my question. Weird that it didn't came up in the suggestion list of already asked questions, or maybe I didn't saw it. – JoJo Dmlx Nov 07 '15 at 10:15
  • @Jarod42 I agree your link better covers this question in particular. I removed my Mjölnir vote – Cory Kramer Nov 08 '15 at 13:41
  • The focus on "primitive" arrays is somewhat of a red herring: you can't do this with `std::vector` either (it won't compile). – John Zwinck Nov 08 '15 at 13:56
  • 1
    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 Nov 08 '15 at 16:03

1 Answers1

0

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.

John Zwinck
  • 239,568
  • 38
  • 324
  • 436