Sign in to follow this  
maya18222

polymorphism

Recommended Posts

Hi, Could someone possibly explain why when using objects rather than pointers in an array container, polymorhism doesn't work? eg
class a
{
virtual void func();
}

class b : public a
{
void func();
}

a aa;
b bb;

vector<a> v;
v.pushback(aa);
v.pushback(bb);

vector<a*> v;
v.pushback(&aa);
v.pushback(&bb);


From my understanding, its due to "slicing", so that when you add an object to a vector that contains objects and not pointers, a copy is passed in, and if that vector stores base types, and you're adding in a derived, the derived data is lost, as only the base data is used to make a copy that gets pushed onto the array. Yet when using an array of pointers, you're still refering to the original, and not a copy. This pointer, has access to the classes v-table, which it uses to access the correct virtual function, if any. Also, i gather that vtables are only created if any of the functions are virtual Now, 1. Take for example, a pointer to bb, Where exactly is this pointer looking to, to get access to the vtable? does each derived type also have a pointer to the vtable? If the pointer is the address to the start of the class object, how does it know where the vtable is? 2. If the vtable pointer is stored somewhere for the pointer type, why cant it also be stored somewhere for the object type?

Share this post


Link to post
Share on other sites
Let's illustrate this with a different example.

struct A {
virtual ~A() {}
virtual void print_me(void) {
std::cout << name << std::endl;
}

std::string name;
};

struct B : A {
virtual ~B() {}
virtual void print_me(void) {
std::cout << name << ":" << age << std::endl;
}

std::string name;
int age;
};

On a 32 bit system with a 8 byte std::string implementation, A and B objects will probably end up looking like:

A:
|----------------|--------------------------------|
| vtable pointer | std::string name |
|----------------|--------------------------------|
B:
|----------------|--------------------------------|----------------|
| vtable pointer | std::string name | int age |
|----------------|--------------------------------|----------------|

Now let's get an array of two As:

|a[0] |a[1] |
|----------------|--------------------------------|----------------|--------------------------------|
| vtable pointer | std::string name | vtable pointer | std::string name |
|----------------|--------------------------------|----------------|--------------------------------|

Now, what happens when you try copying a B onto a[0]? There's room for the name, but no room for the age, since the area that would be the age is actually taken up by a[1], so only the data in A will be copied. This is slicing. Also, since there's no way to allocate more room for B's data, the vtable pointer will stay a pointer to A's vtable.

Does that make sense?

Share this post


Link to post
Share on other sites
Quote:
Original post by maya18222
From my understanding, its due to "slicing", so that when you add an object to a vector that contains objects and not pointers, a copy is passed in, and if that vector stores base types, and you're adding in a derived, the derived data is lost, as only the base data is used to make a copy that gets pushed onto the array.


Yes, exactly.

Quote:
Yet when using an array of pointers, you're still refering to the original, and not a copy.


Well, the pointer refers to the original, but sure.

Quote:
This pointer, has access to the classes v-table, which it uses to access the correct virtual function, if any.


That's nothing to do with it. You always "have access" to the v-table. But when you have a value of the base type, it must have a vtable corresponding to the base type. When you assign from a Derived to a Base, or copy-construct a Base from a Derived, "slicing" occurs because the whole Derived object can't "fit". Thus only the Base part is copied as far as the normal members go. Then, the vtable part is unchanged because changing it would lie that the Derived-specific data is there, when it isn't. See SiCrane's illustration.

Of course, all of this ignores that "vtables" are not mentioned in the C++ spec and are only one way that the compiler might implement polymorphism. Still, the net effect is "as if" in all cases.

Quote:
Also, i gather that vtables are only created if any of the functions are virtual


Nothing in the standard prevents the compiler from creating them, but it's not in the spirit of the language. (Nothing in the standard prevents the compiler from randomly inserting thousands of NOPS in random places, either.) The philosophy is "you don't pay for (or get) what you don't ask for".

Quote:

1. Take for example, a pointer to bb, Where exactly is this pointer looking to, to get access to the vtable?


It points to the beginning of the object's actual members. The relative location of the vtable, if any, is implementation-specified.

Quote:
does each derived type also have a pointer to the vtable?


In a vtable system, each derived type's layout in memory includes a pointer to the vtable, yes. This is of course more complicated when multiple inheritance is involved.

Quote:
If the pointer is the address to the start of the class object, how does it know where the vtable is?


The compiler has a convention saying where it is. The most common convention is "immediately before the object in memory".

Quote:
2. If the vtable pointer is stored somewhere for the pointer type, why cant it also be stored somewhere for the object type?


It is. But it points to the base class' vtable. It has to, because the object only stores the base's members, and any derived class' vtable could refer to functions that expect that derived class' members to be present, which they aren't.

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this