Allegro.cc - Online Community

Allegro.cc Forums » Programming Questions » Pointer Hell Between Base/Derived Classes

This thread is locked; no one can reply to it. rss feed Print
Pointer Hell Between Base/Derived Classes
Kris Asick
Member #1,424
July 2001

Although I don't have a problem anymore, I would like an explanation regarding the following headaches I went through.

I learned something interesting today... I'll bet a lot of you C++ programmers know this already but finding it out suddenly made things easier:

Pointers to arrays of objects derived from a base class != Pointers to arrays of objects of the base class

In making my game engine, one of the things it relies on is being able to make derived classes for sprites, map tiles, etc., without having to have those classes programmed into the engine itself, which itself isn't very difficult, but the engine needs to be able to store and access those derived objects, which was the not-so-simple part, made worse for things like map tiles which are stored in 2D arrays.

In trying to make this work, I kept running into crashes, constantly. I eventually started comparing pointers and discovered that the arrays of derived-object pointers, in being converted to arrays of base-object pointers, were no longer the same as before... rather, the base-object pointers were as though the array had been made out of base objects instead of derived objects.

So when communicating derived objects to base objects, I couldn't find any way to do it at an object or array level, I had to work solely with pointers, otherwise the object data is improperly interpreted in the base class object as a result of the base class generally being smaller (for memory usage) than the derived class.

To put this in perspective, here are two pieces of code. The first one doesn't work. The one below does. From my perspective, it should seem like both should work, but only the bottom one does.

1// This code does not work.
2 
3class B_OBJECT {
4 protected:
5 int baseval;
6};
7 
8class D_OBJECT : public B_OBJECT {
9 int derivedval;
10};
11 
12B_OBJECT ***barray;
13 
14void BuildBaseArray (B_OBJECT **in_array, int d1, int d2)
15{
16 int z, zz;
17 
18 barray = new B_OBJECT**[d1];
19 for (z = 0; z < 20; z++)
20 {
21 barray[z] = new B_OBJECT*[d2];
22 for (zz = 0; zz < d2; zz++)
23 barray[z][zz] = &in_array[z][zz];
24 }
25}
26 
27void Test2DArray (void)
28{
29 D_OBJECT **darray;
30 int z;
31 
32 darray = new D_OBJECT*[20];
33 for (z = 0; z < 20; z++) darray[z] = new D_OBJECT[50];
34 
35 BuildBaseArray((B_OBJECT**)darray,20,50);
36}

---

1// This code works.
2 
3class B_OBJECT {
4 protected:
5 int baseval;
6};
7 
8class D_OBJECT : public B_OBJECT {
9 int derivedval;
10};
11 
12B_OBJECT ***barray;
13 
14void BuildBaseArray (B_OBJECT ***in_array, int d1, int d2)
15{
16 int z, zz;
17 
18 barray = new B_OBJECT**[d1];
19 for (z = 0; z < 20; z++)
20 {
21 barray[z] = new B_OBJECT*[d2];
22 for (zz = 0; zz < d2; zz++)
23 barray[z][zz] = in_array[z][zz];
24 }
25}
26 
27void Test2DArray (void)
28{
29 D_OBJECT ***darray;
30 int z, zz;
31 
32 darray = new D_OBJECT**[20];
33 for (z = 0; z < 20; z++)
34 {
35 darray[z] = new D_OBJECT*[50];
36 for (zz = 0; zz < 50; zz++)
37 darray[z][zz] = new D_OBJECT;
38 }
39 
40 BuildBaseArray((B_OBJECT***)darray,20,50);
41}

I should also note that both compile.

I only vaguely understand why this is as it is, so if someone could spell out a bit better why the top code block fails and the bottom one doesn't, it would be appreciated.

--- Kris Asick (Gemini)
--- http://www.pixelships.com

--- Kris Asick (Gemini)
--- http://www.pixelships.com

Michael Faerber
Member #4,800
July 2004
avatar

I would like to ask you simple question: Why don't you simply use std::vector? I think this could save you from troubles like this ...

--
"The basic of informatics is Microsoft Office." - An informatics teacher in our school
"Do you know Linux?" "Linux? Isn't that something for visually impaired people?"

chidj06
Member #7,844
October 2006
avatar

This is the way in which polymorphism works, the whole system is based arround pointers and dynamic allocation.

A pointer to of object type pointer-to-base class can point to the base class, or any derived classes. i.e you need to use the new, delete keywords, or assign the pointer the address of a valid(ie. class or subclass) object.

But if you declare as an explicit object (non pointer, therefore no dynamic alloction) object is immediately tied to one in memory.

Base_Class* object;

object = new Base_Class;

... or

object = new Derived_Class;

Kris Asick
Member #1,424
July 2001

Quote:

I would like to ask you simple question: Why don't you simply use std::vector? I think this could save you from troubles like this ...

The thought of having to pass a vector of vectors of pointers to derived objects to a function induces pain. I do use vectors for some things, nothing that complicated though.

--- Kris Asick (Gemini)
--- http://www.pixelships.com

--- Kris Asick (Gemini)
--- http://www.pixelships.com

Jakub Wasilewski
Member #3,653
June 2003
avatar

Quote:

&in_array[z][zz];

The problem lies in this statement. in_array[z] is a B_OBJECT*, which is basically the same as a B_OBJECT[] - a one dimensional array. The problem is that the final [zz] uses pointer arithmetic only - and the arithmetic has no way to know that this is actually an array of D_OBJECTs. So, the address is calculated as follows:

start_address_of_in_array_z + zz * sizeof(B_OBJECT)

This is obviously wrong - but pointer arithmetic is based only on the type of the pointer, not the type of the actual objects within.

So, why does the second approach work? Because, unlike B_OBJECT and D_OBJECT, B_OBJECT* and D_OBJECT* are actually the same size, so the indexing operation [zz] returns the proper address regardless of the actual type being pointed to.

---------------------------
[ ChristmasHack! | My games ] :::: One CSS to style them all, One Javascript to script them, / One HTML to bring them all and in the browser bind them / In the Land of Fantasy where Standards mean something.

Michael Faerber
Member #4,800
July 2004
avatar

Quote:

The thought of having to pass a vector of vectors of pointers to derived objects to a function induces pain. I do use vectors for some things, nothing that complicated though.

It's not really THAT complicated - look:

void Function(std::vector<std::vector<DerivedObject*> > objs);

// compared to:
void Function(DerivedObject*** objs);

And if you want to avoid the copying of vectors (which happens automatically due to passing the vector to the function), just use a reference to a vector:

void Function(std::vector<std::vector<DerivedObject*> >& objs);

That should give you a nice performance, too.

--
"The basic of informatics is Microsoft Office." - An informatics teacher in our school
"Do you know Linux?" "Linux? Isn't that something for visually impaired people?"

X-G
Member #856
December 2000
avatar

I know this isn't really an answer to your question, but I think that a lot of the headache and confusion you're experiencing now could be easily avoided by not employing technological terrors like the ones in your code samples there in the first place. Rethink your design and how components fit together, and hopefully the solution should become clear.

--
Since 2008-Jun-18, democracy in Sweden is dead. | 悪霊退散!悪霊退散!怨霊、物の怪、困った時は ドーマン!セーマン!ドーマン!セーマン! 直ぐに呼びましょう陰陽師レッツゴー!

nonnus29
Member #2,606
August 2002
avatar

I don't understand why you think that approach would work. If you allocate an array of b_objects, your going to get an array of b_objects. How is the compiler supposed to know you actually wanted an array a particular (out of possibly many) subclass?

I agree with X-G, you're using C++ as 'C + Objects'. As you can see that's not a good approach. If you'd use containers like the others said you wouldn't have these problems.

Audric
Member #907
January 2001

99% of times, what you actually want to put in an array / N-dimension array / STL container is a reference to something. A pointer to a BITMAP, a pointer to a B_OBJECT, etc.
-> polymorphism works
-> adding / removing / displacing items from one "cell" to another will not require creating/destroying/copying full objects, only the 4 or 8 bytes for the pointer.

Kris Asick
Member #1,424
July 2001

All of you affirmed my reason for asking. I didn't quite understand what was happening, and now I do. ;)

Thanks, guys!

--- Kris Asick (Gemini)
--- http://www.pixelships.com

--- Kris Asick (Gemini)
--- http://www.pixelships.com

Go to: