Sign in to follow this  

I Am Totally Lost!

This topic is 4747 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

Ok guys I have a question on using Unions + Inheritance: Here are my classes:
struct IComponent
{
	int m_id;
	IComponent* Resolve()
	{
		return this;
	}
	virtual void Create() = 0;
	virtual void Destroy() = 0;
};

class SDLRenderDevice : public IComponent
{
public:
	SDLRenderDevice()
	{
		m_id = 0;
	}
	void Create()
	{
		printf("SDLRenderDevice::Create\n");
	}
	void Destroy()
	{
		printf("SDLRenderDevice::Destroy\n");
	}
	void Test1()
	{
		printf("SDLRenderDevice::TestSDL\n");
	}
};

class OGLRenderDevice : public IComponent
{
public:
	OGLRenderDevice()
	{
		m_id = 1;
	}
	void Create()
	{
		printf("OGLRenderDevice::Create\n");
	}
	void Destroy()
	{
		printf("OGLRenderDevice::Destroy\n");
	}
	void Test2()
	{
		printf("OGLRenderDevice::TestOGL\n");
	}
};

class DXRenderDevice : public IComponent
{
public:
	DXRenderDevice()
	{
		m_id = 2;
	}
	void Create()
	{
		printf("DXRenderDevice::Create\n");
	}
	void Destroy()
	{
		printf("DXRenderDevice::Destroy\n");
	}
	void Test3()
	{
		printf("DXRenderDevice::TestDX\n");
	}
};

union VideoModes
{
	SDLRenderDevice* SDL;
	DXRenderDevice* DX;
	OGLRenderDevice* OGL;
};

Ok now here is my little test program:
int main(int argc, char* argv[])
{
	VideoModes cmode;

	printf("%i\n\n",sizeof(VideoModes));

	IComponent *component;
	component = new SDLRenderDevice;

	component->Create();

	if(component->m_id == 0)
	{
		cmode.SDL = reinterpret_cast< SDLRenderDevice*>( component );		
	}
	else if(component->m_id == 1)
	{
		cmode.OGL = reinterpret_cast< OGLRenderDevice*>( component );
	}
	else if(component->m_id == 2)
	{
		cmode.DX = reinterpret_cast< DXRenderDevice*>( component );
	}

	cmode.SDL->Test1();
	cmode.OGL->Test2();
	cmode.DX->Test3();

	component->Destroy();
	
	delete component;
	
	return 0;
}

Here is the output:
4

SDLRenderDevice::Create
SDLRenderDevice::TestSDL
OGLRenderDevice::TestOGL
DXRenderDevice::TestDX
SDLRenderDevice::Destroy
Press any key to continue

Now can anyone tell me why the heck does this work! I just cant understand it. So if you can be so kind to explain to me the entire process.

Share this post


Link to post
Share on other sites
Non-virtual functions are statically bound. That is the code to call them is worked out at compile time. Member functions are essentially equivalent to non member functions that take an extra pointer - the this pointer, so your SDLRenderDevice::Test1() function from the compiler's point of view is essentially the same as:
SDLRenderDeviceTest1(SDLRenderDevice* this)
{
printf("SDLRenderDevice::TestSDL\n");
}

Note how the code is completely independant of the passed parameter. You could even pass it a null pointer and it would still work.

By reinterpreting an object you tell the compiler to ignore the fact that it's a Y and treat it as an X. For statically bound member functions this means that the compiler will generate calls to those functions and pass the object as the this parameter. The actual function to call is determined by the compiler purely at compile time and is based off of the type of the object. If you've told the compiler that the object is actually something else then it'll happily play along. So your lines:
IComponent *component;
component = new SDLRenderDevice;
component->Create();
if(component->m_id == 0)
{
cmode.SDL = reinterpret_cast< SDLRenderDevice*>( component );
}
else if(component->m_id == 1)
{
cmode.OGL = reinterpret_cast< OGLRenderDevice*>( component );
}
else if(component->m_id == 2)
{
cmode.DX = reinterpret_cast< DXRenderDevice*>( component );
}
cmode.SDL->Test1();
cmode.OGL->Test2();
cmode.DX->Test3();


are effectively equivalent to:
SDLRenderDeviceTest1(reinterpret_cast<SDLRenderDevice>(component));
OGLRenderDeviceTest2(reinterpret_cast<OGLRenderDevice>(component));
DXRenderDeviceTest3(reinterpret_cast<DXRenderDevice>(component));


Since members of an object are accessed by offset from the this pointer you can even get code to run if it does access the this pointer:
#include <iostream>

class Test1
{
public:
Test1()
{
member = 32;
}
int func(char c)
{
std::cout << "member = " << member << '\n';
}
int member;
};

class Test2
{
public:
Test2()
{
i = 0;
}
void doSomething(int c, Test1 t)
{
std::cout << "i = " << i << '\n';
}
int i;
};

int main()
{
Test1 t;
reinterpret_cast<Test2*>(&t)->doSomething(17, t);
}



This will print i = 32. This is because the objects have the exact same memory layout - an int. So when the compiler tries to access the i member of the the reinterpreted t object it actually gets the member member of the Test1 object, because member is exactly where i would be if t really was a Test2 object.

Enigma

Share this post


Link to post
Share on other sites
That works because in this particular instance, you are very lucky (or unlucky, depends on perspective). You create an SDLRenderDevice, that is true, but each of those pointers end up pointing to the same exact place in memory. It works because the physical memory layout of all 3 objects is exactly the same (look at the definitons of them, they are for all practical purposes identical). When you make a function call, the code is not stored as part of the object (think about how wasteful that would be). Instead you call what amounts to a global function, implicitily passing a pointer to the appropriate object to it (which is there 'this' comes from).

Try replacing the calls to Test1, Test2, and Test3 will calls to Create. You'll get
SDLRenderDevice::Create
for all of them. In this case, there is a lookup into a virtual table happening, and the vtable 'is' stored as part of an object. So even though you call DX->Create(), the lookup brings it to the 'correct' function.

Share this post


Link to post
Share on other sites
Guest Anonymous Poster
Drew: The reason why you can do most of this is because of two concepts that are said to exist in any true OOP Language: Dynamic Binding and Polymorphism. Dynamic Binding determines which routines to call for a particular object at runtime. Polymorphism is the ability of different objects to respond, each in its own way, to the same message.

I tried to explain your code as best as I could, I am not completely sure that I have explained it right, so if anyone knows points that I have explained wrong I am glad that you correct me.

VideoModes cmode;
--> statically allocates a union of type VideoMode

printf("%i\n\n",sizeof(VideoModes));
--> prints 4 because VideoModes consists of a single union. It has 3 pointers in it. The SizeOf a Union is the size of its largest member. Pointers are 4 bytes.

IComponent *component;
--> ptr to your IComponent base class

component = new SDLRenderDevice;
--> calls SDLRenderDevice's default constructor, returns a pointer to a SDLRenderDevice and then the pointer is down-converted to a type of IComponent

component->Create();
--> prints "SDLRenderDevice::Create\n"

if(component->m_id == 0)
{
cmode.SDL = reinterpret_cast( component );
}
--> Casts IComponent as a SDLRenderDevice
--> Other 2 else ifs are skipped

cmode.SDL->Test1();
--> prints "SDLRenderDevice::TestSDL\n"
cmode.OGL->Test2();
--> prints "OGLRenderDevice::TestOGL\n"
cmode.DX->Test3();
--> prints "DXRenderDevice::TestDX\n"

cmode is statically allocated, so you do not need to initialize the pointers to be able use them

component->Destroy();
--> prints "SDLRenderDevice::Destroy\n" because component was casted above to an SDLRenderRevice*

delete component;
--> calls the default destructor which is not specifically defined in the program

return 0;
}

Share this post


Link to post
Share on other sites
Quote:
Original post by Anonymous Poster
Drew: The reason why you can do most of this is because of two concepts that are said to exist in any true OOP Language: Dynamic Binding and Polymorphism. Dynamic Binding determines which routines to call for a particular object at runtime. Polymorphism is the ability of different objects to respond, each in its own way, to the same message.

I tried to explain your code as best as I could, I am not completely sure that I have explained it right, so if anyone knows points that I have explained wrong I am glad that you correct me.

VideoModes cmode;
--> statically allocates a union of type VideoMode

printf("%i\n\n",sizeof(VideoModes));
--> prints 4 because VideoModes consists of a single union. It has 3 pointers in it. The SizeOf a Union is the size of its largest member. Pointers are 4 bytes.

IComponent *component;
--> ptr to your IComponent base class

component = new SDLRenderDevice;
--> calls SDLRenderDevice's default constructor, returns a pointer to a SDLRenderDevice and then the pointer is down-converted to a type of IComponent

component->Create();
--> prints "SDLRenderDevice::Create\n"

if(component->m_id == 0)
{
cmode.SDL = reinterpret_cast( component );
}
--> Casts IComponent as a SDLRenderDevice
--> Other 2 else ifs are skipped

cmode.SDL->Test1();
--> prints "SDLRenderDevice::TestSDL\n"
cmode.OGL->Test2();
--> prints "OGLRenderDevice::TestOGL\n"
cmode.DX->Test3();
--> prints "DXRenderDevice::TestDX\n"

*EDIT*cmode is pointing to component casted as an SDLRenderDevice*, so that's why the above methods are fine*EDIT*

component->Destroy();
--> prints "SDLRenderDevice::Destroy\n" because component was casted above to an SDLRenderRevice*

delete component;
--> calls the default destructor which is not specifically defined in the program

return 0;
}


The AP was me (thought I was logged in) and this line of code (I was going to edit the mistake until I noticed it showed up as AP)
if(component->m_id == 0)
{
cmode.SDL = reinterpret_cast< SDLRenderDevice*>( component );
}


--> Really makes cmode.SDL point to component casted as an SDLRendereDevice*

Share this post


Link to post
Share on other sites
Good point AP. I was concentration so much on the function call I missed the union. A union can in effect be used exactly the same as a reinterpet_cast.
union VideoModes
{
SDLRenderDevice* SDL;
DXRenderDevice* DX;
OGLRenderDevice* OGL;
};

VideoModes essentially contains a single pointer. When you access VideoModes.SDL you are effectively accessing reinterpret_cast<SDLRenderDevice*>(VideoModes.theActualPointer). Likewise for VideoModes.DX and VideoModes.OGL They are all different names for the same pointer.

Enigma

Share this post


Link to post
Share on other sites
And just to really blow your mind, a reinterpret_cast with a dynamically bound function call by faking a vtable in a class that doesn't actually have one (pointless but fun):
#include <iostream>

class Test1;

typedef void (*functionPtr)(Test1*);

void freeFunction(Test1* t1);

class Base
{
public:
virtual void func() = 0;
};

class Test1
{
public:
Test1()
{
vpointer = &vtable;
vtable = freeFunction;
i = 67;
}
functionPtr* vpointer;
functionPtr vtable;
int i;
};

class Test2
:
public Base
{
public:
virtual void func()
{
}
};

void freeFunction(Test1* t1)
{
std::cout << "freeFunction: " << t1->i << '\n';
}

int main()
{
Test1 t;
reinterpret_cast<Test2*>(&t)->func();
}


Enigma

Share this post


Link to post
Share on other sites
Ok Enigma, Desert Fox and wyrzy, thank you for your explnations - now let me get this straight:

I understand the whole union business about it sharing memory - however, I did not know that "a union can in effect be used exactly the same as a reinterpet_cast." but it makes perfect sense, so thanks for that info.

I tried changing the memory layout of all the classes, but it stil worked - I dont know if there is something that it may be messing up as a side effect or not.

After messing around with it some more I get what you are all saying. I added different variables to each class and then accessed them under the function calls in the main. Since only 1 class has the memory, only those member functions will work correctly, which is what I saw. I also understand that, like Enigma said, the non-virtual functions are static, so thats why you can call them and it works fine - the memory never changes. I see why I could call the functions without any trouble - but if i were to access something of that class in that function, it would fail to work.

I could easily see how this could be abused [wink]. But may I also ask, do you guys think this appraoch I am on would be good for a game engine, specifically handling the components. Right now it seems kind of complex and rahter unnecessary to have to do all of this inorder to use custom renders, so does anyone have a suggestion of what I should do different? Thank you all!

Share this post


Link to post
Share on other sites
Are you trying to design your game engine so that you can use DX, OpenGL, or SDL depending on the user's computer?

First of all, I would be very careful with reinterpret_cast, as it allows you cast any type of pointer to another type of pointer. This means could could cast a char* to a SDLRendererDevice.

Secondly, do you really need to implement your game engine to work with DX, OpenGL, and SDL. While it is good to think ahead, you can also easily burry yourself in loads of work and end up dropping one or two components in the end. Also, sometimes it is a good idea to develop your engine around the API you will be using. Some things are easier to do in DX than in OpenGL. And if you're designing the engine to use SDL as well, then I'm guessing that it will stick to mainly 2D.

I would say that until you realize that you need DX support in your engine, just stick with SDL and OpenGL.

I would need to know more about what you want your engine to be used for to give a proper suggestion. As far as I know, there as no One Game Engine that can be used for FPSs/RPGs/Turn-Based Strategy/MMORPGs/Racing/Puzzle Games and any other Gaming Genre that exists. If anyone does, I'd like to know what it is[wink].

Share this post


Link to post
Share on other sites
Quote:
Original post by wyrzy
Are you trying to design your game engine so that you can use DX, OpenGL, or SDL depending on the user's computer?

First of all, I would be very careful with reinterpret_cast, as it allows you cast any type of pointer to another type of pointer. This means could could cast a char* to a SDLRendererDevice.

Secondly, do you really need to implement your game engine to work with DX, OpenGL, and SDL. While it is good to think ahead, you can also easily burry yourself in loads of work and end up dropping one or two components in the end. Also, sometimes it is a good idea to develop your engine around the API you will be using. Some things are easier to do in DX than in OpenGL. And if you're designing the engine to use SDL as well, then I'm guessing that it will stick to mainly 2D.

I would say that until you realize that you need DX support in your engine, just stick with SDL and OpenGL.

I would need to know more about what you want your engine to be used for to give a proper suggestion. As far as I know, there as no One Game Engine that can be used for FPSs/RPGs/Turn-Based Strategy/MMORPGs/Racing/Puzzle Games and any other Gaming Genre that exists. If anyone does, I'd like to know what it is[wink].


Well wyrzy, I want my engine to be the engine that can be "used for FPSs/RPGs/Turn-Based Strategy/MMORPGs/Racing/Puzzle Games and any other Gaming Genre that exists" [lol]. Really! They way I have it now(engine I talked about in the function pointer thread), I can add in DX support quite easily to the base engine. The end user just has to specify that render mode with the flag, and it would make a DX window. However, after discussing that way on taht thread, people suggested that I used a more "practical" approach with the inheritance and stuff. Im still debating on that b/c if the user wanted to added in their own api usage, they would have to overload the Run function in my engine - thus making my engine pointless.

I understand the potential of reinterpret_cast's power, I have nenver used it professionally before [wink], but I have seen it used in a tutorial or two.

As for not adding DX yet, oh dont worry, im not! [wink]. DX is the devil in my opinion and will be until I can take the time and figure it out. I just was thinking ahead since a lot of people do like DX.

A common misconception is that people who are using SDL are aiming for 2D. SDL is for 2D, but when you want 3D you can use a OGL flag. Using OGL through SDL is a heck of a lot easier than using pure OGL through windows. I invite everyone to give it a try - heck I can make 2 comparison programs for everyone. Anyways, the main reason I am using SDL is because it has a whole slew of features I can use as well as OGL that make DX cry for its mommy [lol]. If you have ever taken a look at all the neat SDL stuff there is, there is support for CDrom functions,Joystick, and tons of others - and they are a lot easier to use than DX stuff. Not only that, I can use SDL_Net for networking and FMOD/SDL_mixer for audio as well as *ANY* OpenGL functions - do you know those wglGetProc functions in windows? SDL has its own that lets you do the same - w/o having to do it through windows so it is platform independent - not that I am making games for anything other than windows though.

Anyways the long and short of it is that I am aiming for an engine that can be used for ANY type of game - that is why my design process has to be so freaking abstract - I have to allow changes in anything yet make something so it all works together. That is going to be a challenge, but I have all the main components done, and they are independedt of each other as well as the engine - I just need to figure out how to interface them with the engine to allow maximum customixation by the end user.

Thanks again for your input - I may have to PM you to talk some more about my engine ideas and see what you think about them [grin]. Take care!

Share this post


Link to post
Share on other sites
Quote:
Original post by Drew_Benton
I could easily see how this could be abused [wink]. But may I also ask, do you guys think this appraoch I am on would be good for a game engine, specifically handling the components. Right now it seems kind of complex and rahter unnecessary to have to do all of this inorder to use custom renders, so does anyone have a suggestion of what I should do different? Thank you all!


Instead of having the union, just use an IComponent *. If you need to call a function specific to one of the implementations, cast at that point. (But try to design to minimize the amount that you have to do this.)

Share this post


Link to post
Share on other sites

This topic is 4747 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

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