Skip to main content
GameDev.net gamedev.net
🔒 Locked

Pertains To static_cast

Started by Deception666 Mar 10, 2006 at 1:49 AM 9 replies 1.1k views
Original Post
Deception666
Deception666
I have been reading "C++ for Game Programmers" for the past couple of weeks. I enjoy the book, but there is a code example that has me stumped and ashamed that I don't really understand what the author is trying to say. Let me give you an example. If you happen to have the book, the pages are 264 – 267.

class IRenderable
{
public:
	bool Render( ) = 0;
	// …
};

class GameEntPhysical : public GameEntity,
			    public IRenderable
{
public:
	bool Render( ) ;
	// …
};

void * GameEntPhysical::QueryInterface( Interface interface ) const
{
	if (interface == IRENDERABLE)
	{
		IRenderable * pRender = static_cast< IRenderable * >(this);
		return (void *)(pRender);
	}
	
	return NULL;
};

// ...
void * pInterface = obj.QueryInterface(IRENDERABLE);
if (pInterface)
{
    IRenderable * pRender = static_cast< IRenderable * >(pInterface);
}
// ...

What has me ashamed to call myself a programmer is the next passage?
Quote:
Notice that we first cast the this pointer to the type of pointer we want, and then we return it as a plain void pointer. Even though it looks like an unnecessary step, that casting will most likely change the actual value of the pointer. Without it, the returned value could not be safely cast to the correct interface type.
I'm not quite fallowing the logic here by when the author says, "Without it, the returned value could not be safely cast to the correct interface type." How does taking the static_cast, which should be a different location in memory, and returning that interfaces location through the use of a void * "most likely change the actual value of the pointer?" Shouldn't returning just pRender or the void * of pRender be the same thing? How is it possible for the cast to change the value of the pointer? Thanks for the help and insight.
wasd
wasd
I came up against the exact same problem before, when I was just using a C style cast to one of the base classes. It's to do with the fact that the class has more than one base class (I think anyway, I kind of just guessed at what was causing it). The function tables for each base class can't be at the same address, so a static_cast or dynamic_cast will change the pointer to the address of the function table for the base class you are casting to. (If that makes sense).
CipherCraft
CipherCraft
Hi,

That's the problem with multiple inheritance.

GameEntity          IRenderable    |                   |    +---------+---------+              |       GameEntPhysical


If you have a pointer to GameEntPhysical, should a cast to void* be a pointer to GameEntity or IRenderable? This is a decision the compiler can not make, so you have to make that decision for it.

hth,
CipherCraft
Deception666
Deception666
Quote:
Original post by f8k8
I came up against the exact same problem before, when I was just using a C style cast to one of the base classes. It's to do with the fact that the class has more than one base class (I think anyway, I kind of just guessed at what was causing it). The function tables for each base class can't be at the same address, so a static_cast or dynamic_cast will change the pointer to the address of the function table for the base class you are casting to. (If that makes sense).


Thank you for the reply. I do understand what the compiler does under the hood when you specify a static cast, but you really haven't answered the question at hand.

Quote:
Original post by f8k8
Hi,

That's the problem with multiple inheritance.



GameEntity IRenderable
| |
+---------+---------+
|
GameEntPhysical



If you have a pointer to GameEntPhysical, should a cast to void* be a pointer to GameEntity or IRenderable? This is a decision the compiler can not make, so you have to make that decision for it.

hth,
CipherCraft


Thank you for the reply. Yes, I understand that this is a small bump in the road with multiple inheritance, but the question still has not been answered.

How is it that returning just pRender is different from (void *)(pRender)? I'm still a little tired right now, but I'll come back to this in a little bit. I kind of see one possible problem that the author was talking about. If we have another GameEntPhysical that also inherits from IRenderable interface, how is the compiler able to position pointer to the interface? I'm guessing that is what the author is trying to state. But, how is it that the (void *)(pRender) will change the pointer when it is returned so that the calling function can cast it back to the correct type?

Thanks
Enigma
Enigma
The c-style cast to void * is redundant, although it does provide documentation (a second static_cast would have been clearer though). Only the first cast is required, although as written it is ill-formed (static_cast may not cast away constness).

The problem here is not so much multiple inheritance as it is subverting the type system by using void *s. I consider it a poor design.

Σnigma
Deception666
Deception666
Quote:
Original post by Enigma
The c-style cast to void * is redundant, although it does provide documentation (a second static_cast would have been clearer though). Only the first cast is required, although as written it is ill-formed (static_cast may not cast away constness).

The problem here is not so much multiple inheritance as it is subverting the type system by using void *s. I consider it a poor design.

Σnigma


So, what was the author trying to say then? C++ purist I can tell. Yes, it is subverting the type system and the chapter does talk about the use of the RTTI and without RTTI, but I'm not concerned about the design. Just trying to figure out more of what the aurthor is trying to state.
Enigma
Enigma
The issue is that a GameEntPhysical * to a GameEntPhysical object may have a different value to an IRenderable * to a GameEntPhysical object, even though they point to the same object, because of the ways different compilers handle multiple inheritance. Converting a pointer to a void * and then to another pointer value will preserve the pointer value, but will not make any necessary adjustments depending on the types of the pointers, since a void * has no type information. For example on one of my compilers:
Quote:
#include

struct Base1
{
};

struct Base2
{
};

struct Derived
:
public Base1,
public Base2
{
};

int main()
{
Derived d;
Derived * dp = &d
Base2 * b2p = static_cast< Base2 * >(dp);
void * vp = static_cast< void * >(dp);
Base2 * b2p2 = static_cast< Base2 * >(vp);
std::cout << b2p << '\n' << b2p2 << '\n';
}

gives:

1245060
1245052


The Base2 * that was converted via a void * has a different value to a Base2 * that was directly converted because the void * didn't know to make the adjustment and didn't pass on any type information. The second Base2 * therefore has an incorrect value and will likely cause a crash (or worse, corrupt memory) when accesed.

Σnigma
Emmanuel Deloget
Emmanuel Deloget
As Enigma said, the first static_cast<> is not needed (the cast from a derived class instance to its parent is implicit)

Quote:
Original post by Deception666
Thank you for the reply. I do understand what the compiler does under the hood when you specify a static cast, but you really haven't answered the question at hand.


Let's take an example

#include <iostream>class A{public:   virtual void a() { std::cout << "A::a" << std::endl; }};class B{public:   virtual void b() { std::cout << "B::b" << std::endl; }};class AB : public A, public B{};int main(int argc, char* argv[]){   AB       ab;   A        *a;   B        *b;   a = &ab   b = &ab   void    std::cout << "address of a: " << a << std::endl;   std::cout << "address of b: " << b << std::endl;   void     *va;   void     *vb;   va = (void *)(&ab);   vb = (void *)(&ab);   std::cout << "address of va: " << va << std::endl;   std::cout << "address of vb: " << vb << std::endl;   // uncomment this if you are in the IDE   // std::cin.get();   return 0;}


If you execute this then you'll see that the address of a and the address of b are different but va and vb are the same

The author simply state that if he don't do a static cast (or an implicit cast) then he'll loose the type information he wants and he'll be unable to get his correct pointer back.

To summarize:
AB* -> B* -> void* -> B* ==> OKAB* -> void* -> B* ==> may not be ok


Hope you understand his point better (just never do whet he does [smile])

Regards,
Deception666
Deception666
Thank you both. I believe I understand now.
Conner McCloud
Conner McCloud
Quote:
Original post by Deception666
Thank you both. I believe I understand now.

Now that you understand what the author did, try and understand that you should never do it yourself. I just read that little segment of that book [I love Amazon's Look Inside feature], and its an extremely brittle design. Any time you find yourself throwing away type safety, think long and hard about whether or not its the only solution. Given this system, you might as well not be using inheritance at all.

CM
Nitage
Nitage
Can this problem ever arise without the programmer deliberatley circumventing the type system?

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.