www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - Cast vs Virtual Method vs TypeId?

reply Jonathan Marler <johnnymarler gmail.com> writes:
I'd like to hear peoples thoughts on the various solutions for 
the following problem.  Say you have some hierarchy of classes 
like:

class GameObject {
   // ...
}
class Entity : GameObject {
   // ...
}
class Player : Entity {
   // ...
}
class Enemy : Entity {
   // ...
}
// ...

Assume you have a function that accepts a GameObject but does 
something special if that GameObject happens to be an instance of 
the Player class. How would you go about determining this? (Note: 
assume you need to make the distinction at runtime, so you can't 
use a static if with an 'is' expression inside a template.)

I'd like to hear what people think in 2 cases
   1) Only need to know if it's an instance of the Player class.
   2) Need to know if it's an instance of the Player class AND 
need an instance of the Player class.

The potential solutions I thought of were:

1) typeid (Only handles case 1)

if(typeid(obj) == typeid(Player) {
   // treat as player object
}

If you don't need an instance of the Player class, maybe this one 
is good? I don't know in terms of efficiency if this is better, 
or casting is better.  Maybe cast uses the typeid under the hood 
to determine if the cast can be performed?

2) Custom Type Enum (Only handles case 1)

enum GameObjectType {
   gameObject, player, ...
}
class GameObject {
   GameObjectType type;
}

if(obj.type == GameObjectType.player) {
   // treat it as a player object
   // Note: if you need to use Player class specific fields
   //       then you'll need to use the cast or virtual function
   //       design which kinda defeats the purpose of this design 
in the
   //       case where it is actually a Player object.
}

This method may be similar to the typeid method, not sure since I 
don't know how typeid works under the hood.  If it's similar then 
this would just be a waste of memory and should not be used in 
favor of the typeid method.

3) Cast (Handles case 1 and 2)

auto player = cast(Player)obj;
if(player) {
   // treat it as a player object
}

I don't know how cast works under the hood so it's hard to 
compare it to other methods.  Any information on how cast works 
under the hood would be great.

4) Virtual Method (Handles case 1 and 2)

class GameObject {
   Player asPlayer() { return null; }
}
class Player {
   override Player asPlayer() { return this; }
}

auto player = obj.asPlayer;
if(player) {
   // treat it as a player object
} else {
   // treat it as any other game object
}

This solution handles the same cases as regular casting, but I 
can't compare them since I don't know how casting works under the 
hood.  One thing to consider is that this method scales linearly 
since you need to add a new virtual method for every type you 
want to support, so the vtable gets larger as you add more types.


Any other solutions? Thoughts?  Thanks in advance for the 
feedback.
Jun 29 2016
next sibling parent reply rikki cattermole <rikki cattermole.co.nz> writes:
On 30/06/2016 12:25 PM, Jonathan Marler wrote:
 I'd like to hear peoples thoughts on the various solutions for the
 following problem.  Say you have some hierarchy of classes like:

 class GameObject {
   // ...
 }
 class Entity : GameObject {
   // ...
 }
 class Player : Entity {
   // ...
 }
 class Enemy : Entity {
   // ...
 }
 // ...

 Assume you have a function that accepts a GameObject but does something
 special if that GameObject happens to be an instance of the Player
 class. How would you go about determining this? (Note: assume you need
 to make the distinction at runtime, so you can't use a static if with an
 'is' expression inside a template.)
void func(GameObject o) { if (Player player = cast(Player)o) { // something special } }
Jun 29 2016
parent Jonathan Marler <johnnymarler gmail.com> writes:
On Thursday, 30 June 2016 at 00:27:57 UTC, rikki cattermole wrote:
 On 30/06/2016 12:25 PM, Jonathan Marler wrote:
 Assume you have a function that accepts a GameObject but does 
 something
 special if that GameObject happens to be an instance of the 
 Player
 class. How would you go about determining this? (Note: assume 
 you need
 to make the distinction at runtime, so you can't use a static 
 if with an
 'is' expression inside a template.)
void func(GameObject o) { if (Player player = cast(Player)o) { // something special } }
Thanks for the response. Do you also have any information on how cast works under the hood?
Jun 30 2016
prev sibling parent QAston <qaston gmail.com> writes:
On Thursday, 30 June 2016 at 00:25:53 UTC, Jonathan Marler wrote:
 I'd like to hear peoples thoughts on the various solutions for 
 the following problem.  Say you have some hierarchy of classes 
 like:

 class GameObject {
   // ...
 }
 class Entity : GameObject {
   // ...
 }
 class Player : Entity {
   // ...
 }
 class Enemy : Entity {
   // ...
 }
 // ...

 Assume you have a function that accepts a GameObject but does 
 something special if that GameObject happens to be an instance 
 of the Player class. How would you go about determining this? 
 (Note: assume you need to make the distinction at runtime, so 
 you can't use a static if with an 'is' expression inside a 
 template.)

 I'd like to hear what people think in 2 cases
   1) Only need to know if it's an instance of the Player class.
   2) Need to know if it's an instance of the Player class AND 
 need an instance of the Player class.

 The potential solutions I thought of were:

 1) typeid (Only handles case 1)

 if(typeid(obj) == typeid(Player) {
   // treat as player object
 }

 If you don't need an instance of the Player class, maybe this 
 one is good? I don't know in terms of efficiency if this is 
 better, or casting is better.  Maybe cast uses the typeid under 
 the hood to determine if the cast can be performed?

 2) Custom Type Enum (Only handles case 1)

 enum GameObjectType {
   gameObject, player, ...
 }
 class GameObject {
   GameObjectType type;
 }

 if(obj.type == GameObjectType.player) {
   // treat it as a player object
   // Note: if you need to use Player class specific fields
   //       then you'll need to use the cast or virtual function
   //       design which kinda defeats the purpose of this 
 design in the
   //       case where it is actually a Player object.
 }

 This method may be similar to the typeid method, not sure since 
 I don't know how typeid works under the hood.  If it's similar 
 then this would just be a waste of memory and should not be 
 used in favor of the typeid method.

 3) Cast (Handles case 1 and 2)

 auto player = cast(Player)obj;
 if(player) {
   // treat it as a player object
 }

 I don't know how cast works under the hood so it's hard to 
 compare it to other methods.  Any information on how cast works 
 under the hood would be great.

 4) Virtual Method (Handles case 1 and 2)

 class GameObject {
   Player asPlayer() { return null; }
 }
 class Player {
   override Player asPlayer() { return this; }
 }

 auto player = obj.asPlayer;
 if(player) {
   // treat it as a player object
 } else {
   // treat it as any other game object
 }

 This solution handles the same cases as regular casting, but I 
 can't compare them since I don't know how casting works under 
 the hood.  One thing to consider is that this method scales 
 linearly since you need to add a new virtual method for every 
 type you want to support, so the vtable gets larger as you add 
 more types.


 Any other solutions? Thoughts?  Thanks in advance for the 
 feedback.
This problem is not D specific, there are lots of different approaches. If you're not going to add new types to the hierarchy very often the visitor pattern is a good choice, see[1]. You can also use composition instead of inheritance to share code by using mixins + templates. Or you can try more traditional entity-component-systems. Take a look at game engines written in D, they may have some good D specific ideas already. http://www.deadalnix.me/2012/08/25/visitor-pattern-revisited-in-d/
Jun 30 2016