Sunday, December 07, 2003

Re: Type Casting


basically what i was more interested in is how does the parent know who is the derived and vice versa.

The parent NEVER knows who it's children/grand children/... are. Only the derived classes are aware of their parent.

Considering the object on the heap...

// Heap
------------- // address
| int m_dataA // 0
| int m_dataB // 3
------------- // 7

1.
either as you showed both A and B data members are stored in a linear fashion ie in contiguous memory locations. so when i need the entire B, the next sizeof extra B data members has to be considered and that will be B. the pointer always points to A and then based on the type cast simply considers the next memory locations. i hope i am clearer this time.

Yeah you're right... Pointers of type A as well as pointers of type B will point to memory location 0. It's just that with type A pointers, you will only have access to sizeof( A ) which is 0 to 3. With type B pointers, you have access to everything, 0 to 7, which is sizeof( B ).

2.
for larger classes with a large amount of inheritance this linear memory location storage would be inefficient. just like a normal os memory management we could break the memory into partitions and allocate memory for objects in broken discontinous locations. then a table would be required to map various memory locations to the objects. i am just guessing all of this. this sounds inefficient but for larger classes? i know this is a bit into actual vm design / memory management, but in case you guys have any idea.

Actually, ALL objects have their data stored linearly. This is quite efficient. How would breaking it up into partitions be more efficient? I don't see it. Even if the class has 100's of data members... it really is NOT that much considering there are arrays with 1000's of elements which are stored in memory contiguously.

If the data is stored linearly, when you have a pointer to that object, accessing the data members is just a constant time operation. If the object is broken up you will have indirections. Also, to support inheritance, it is almost essential to have it stored linearly. This is how you can have base type pointers to derived type objects.

is the v-table in c++ associated with each of the data-member locations itself pointing to the parent, derived locations etc ? i guess this table really gets complicated with multiple inheritance.

Each class (not object) in a hierarchy has it's own v-table. That v-table just contains the addresses of virtual functions. Consider this ex.

// C++

class A
{
private:
    int m_dataA;

public:
    A() : m_dataA( 0 ) {};
    virtual ~A() {};
    virtual void fun() { std::cout << "funA"; };
};

class B : public A
{
private:
    int m_dataB;

public:
    B() : m_dataB( 0 ) {};
    virtual ~B() {};
    /*virtual*/ void fun() { std::cout << "funB"; };
};

So both class A and class B will get a two index v-table - one index for the destructor and another for fun(). Also, each object of A and B will get a vptr that points to this table.

// A object on heap

|------------
| int m_dataA
| vptr ---------------> v-table
|------------           ------------------
                        | address of ~A()
                        | address of fun()
                        -------------------

// B object on heap

|------------
| int m_dataA
| int m_dataB
| vptr ---------------> v-table
|------------           ------------------
                        | address of ~B()
                        | address of fun()
                        -------------------

So now, if you have an A pointer pointing to a B object and you call fun();

A* p = new B;

p->fun();

you want it to call B's fun(), not A's. This is where the indirection occurs. The compiler will just make the call through the vptr. I guess maybe something like... *(p->vptr[1])();. This will invoke B's fun().

Same way, if you had another class C, that derives from A, it would also get a v-table with 2 indices. And again if you had an A pointer to a C object and call fun(), it would go through the v-table and the correct fun() would get called. This is the power of polymorphism. You can keep adding new classes and don't have to modify your code. It just works!

No comments: