Jump to content
  • Advertisement
Sign in to follow this  
tenpoundbear

Inheritance - What type of object is this?

This topic is 3123 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

Hi again guys :-D With this code
// Global Variables
Specification **specification;

void setupGame(  )
{
	specification = new Specification *[ 2 ];
	specification[ 0 ] = new FighterSpec( "F1" );
	aircraft = new Aircraft[ 5 ];
	aircraft[ 0 ].setSpec( specification[ 0 ] );
}
What type of object is "specification[ 0 ]"? I thought it is a "FighterSpec" object, but when you go to the last line of code where the setSpec function is being called the compiler gives me an error
error C2664: 'Aircraft::setSpec' : cannot convert parameter 1 from 'Specification *' to 'FighterSpec *'


Now I understand what the error is saying but I don't understand how it can be that specification[0] is a Specification object when I've used the constructor for FighterSpec class. Basically because I have the code 'new FighterSpec' I thought it would be of FighterSpec type... but the compiler is saying it is not. FighterSpec inherits from Specification by the way. This is the function definition too.
void Aircraft::setSpec( FighterSpec *spec )
{
	shipSpec = spec;
}
Again... very much appreciated guys:)

Share this post


Link to post
Share on other sites
Advertisement
specification[0] is a pointer to a Specification object but as FighterSpec inherits from a Specification then its fine to assign. setSpec takes a FighterSpec but you've sort of hidden the fact that specification[0] is a FighterSpec object

Before calling setSpec you can cast specification to a FighterSpec object


FighterSpec *fighterSpec = dynamic_cast<FighterSpec*>(specification[0])
if(fighterSpec != NULL)
{
aircraft[ 0 ].setSpec( fighterSpec );
}




Thats fine as long as your absolutly sure that specification[0] is a Fighterspec object (which it is).

Share this post


Link to post
Share on other sites
Hiya,
Whilst the object you created is indeed a 'FighterSpec' object, you have stored in an array of 'Specification' objects. Once you access this array the compiler only knows that it is a 'Specification*'.

If your FighterSpec object did not derive from Specification then you would get a compile error when attempting to store the object in that array.

The simplest solution to your example code is to cast the Specification pointer to a FighterSpec pointer, because with your knowledge of the code base you know this pointer to be a FighterSpec object. However, once your code leaves this function you cannot know that the objects within this list are FighterSpec objects because they could be any object that has derived from Specification. This is the issue the compiler is having, although you can see what you have assigned and created. The compiler can only see that it is an array of Specification pointers.

A more robust solution is to provide the functionality you desire inside the Specification object and only access everything via the Specification object. This works well if you have base functionality shared between multiple objects (though if you wanted to be able to specialize the behaviour on a per-object type basis, the function should be made virtual).

Alternatively, you could provide a Is* functionality in the Specification object so that you can so something similar to

if ( specification->IsFighterSpec() )
{
FighterSpec* fighterSpec = ( FighterSpec* )specification;

fighterSpec->DoSomething();
}

but I don't really like that method personally as it hints at a poorly defined interface hierarchy. With some thought, it is probably fairly simple to design a class hierarchy that provides a more robust implementation. But I can't help much more than that as I'm not aware of what you're trying to do or have much information on the classes.

n!

Share this post


Link to post
Share on other sites
dynamic cast is generally a little sucky, and not something you want to be using. You can however implement your RTTI methods (and there are many ways to do this each with pros and cons). A simple example would be something like:


#define RTTI(ID,BASE) static const unsigned TYPE=ID; virtual Specification* as(unsigned type) { if(type==ID) return this; return BASE ::as(type); }

class Specification
{
public:
static const unsigned TYPE = 0;
template<typename T>
T* as()
{
return (T*)as(T::TYPE);
}

protected:
virtual Specification* as(unsigned type)
{
if(type==Type)
return (Specification*)this;
return 0;
}
};
class FighterSpec : public Specification
{
public:
RTTI(1,Specification);
};
class EuroFighterSpec : public FighterSpec
{
public:
RTTI(2,FighterSpec);
};

// Then....

void someFunc(Specification* spec)
{
FighterSpec* fighter_spec = spec->as<FighterSpec>();
if(fighter_spec)
{
// spec is a FighterSpec
}
}



Share this post


Link to post
Share on other sites
What I would suggest is to make Specification an interface rather than a concrete base class. Concrete subclasses like FighterSpec, BomberSpec, etc. would then implement the abstract behavior of the (I)Specification interface. Instead of the Aircraft class having a function setSpec(FighterSpec), it would have a function setSpec(Specification).

Share this post


Link to post
Share on other sites
Quote:
Original post by RobTheBloke
dynamic cast is generally a little sucky, and not something you want to be using. You can however implement your RTTI methods (and there are many ways to do this each with pros and cons). A simple example would be something like:

*** Source Snippet Removed ***


Thanks for the sniplet. I'm currently considering the different ways to do RTTI in my system and I'm curious, how do you know that your method is considerably faster than dynamic_cast? rather than implementing a system like yours, i've put trust in the fact that the designs have likely chosen the most efficient (and robust) way to implement dynamic_cast type comparisons. Possibly resulting in a simple integer comparison like yours.

At the moment, i dont see any advantage of a homebrew cast validation system over dynamic_cast. Can you show me otherwise?

edit: I've found this link which answers a lot of my questions. Apparently dynamic_cast does a string comparison. http://www.nerdblog.com/2006/12/how-slow-is-dynamiccast.html

sidequestion: Is your method less safe than static_casting, by doing the c-style cast in the as() function?

Share this post


Link to post
Share on other sites
I think you might be going about this the wrong way.

The problem is that your code currently drops type information by storing the FighterSpec object in a generic Specification pointer. Consider: what have you bought yourself? What use is a specification type, if I must downcast to use it?

Perhaps some kind of factory class can be introduced which can keep the correct types around long enough for them to be useful.

On a stylistic note, I prefer to use the constructor to set essential data, which the specification would come under. Using "set" methods can be a brittle approach, it can be very easy to create an object without setting all of its crucial member variables. It also promotes a way of dealing with objects that treats them as raw data holders without any specialised behaviour.

Share this post


Link to post
Share on other sites
Here's what i've come up with. I'd love to have anyone's opinion on it.

RTTI and "Entity" library:

#include "boost/shared_ptr.hpp"
#include <vector>

class IDGen
{
public:
static int GetNextID() { static IDGen s_IDGen; return s_IDGen.ID++; }

private:
IDGen() : ID(0) { }

int ID;
};


#define RTTITypeInfo(KeyGenerator) static int GetTypeID() { static int TypeID = -1; if (TypeID == -1) TypeID = KeyGenerator::GetNextID(); return TypeID; } virtual bool IsType(int t) const { return GetTypeID() == t; }

#define RTTIDerivedTypeInfo(KeyGenerator, Base) static int GetTypeID() { static int TypeID = -1; if (TypeID == -1) TypeID = KeyGenerator::GetNextID(); return TypeID; } virtual bool IsType(int t) const { if (GetTypeID() == t) return true; else return Base::IsType(t); }


class RTTI
{
public:
virtual ~RTTI() { }

RTTITypeInfo(IDGen);
};

class Entity
{
public:
virtual ~Entity() { }

void AddComponent(boost::shared_ptr<RTTI> c) { Components.push_back(c); }

template <typename T>
boost::shared_ptr<T> FindComponent()
{
boost::shared_ptr<T> c;
int tID = T::GetTypeID();

std::vector<boost::shared_ptr<RTTI>>::iterator it;
for (it = Components.begin(); it != Components.end(); ++it)
{
if ((*it)->IsType(tID))
{
c = boost::static_pointer_cast<T>(*it);
break;
}
}

return c;
}

private:
std::vector<boost::shared_ptr<RTTI>> Components;
};




#include <iostream>

class BaseObj : public RTTI
{
public:
virtual ~BaseObj() {}

RTTIDerivedTypeInfo(IDGen, RTTI);

void DoIt() { std::cout<< "Base does it!\n"; }
};

class D1Obj : public BaseObj
{
public:
RTTIDerivedTypeInfo(IDGen, BaseObj);

void DoSomething() { std::cout<< "D1Obj does something!\n"; }
};

class Thinker : public BaseObj
{
public:
RTTIDerivedTypeInfo(IDGen, BaseObj);

void ThinkAboutDoingIt() { std::cout<< "Thinker thinks about it!\n"; }
};



void DoItToABase(Entity &e)
{
boost::shared_ptr<BaseObj> c = e.FindComponent<BaseObj>();
if (c) c->DoIt();
}

void ThinkAboutIt(Entity &e)
{
boost::shared_ptr<Thinker> c = e.FindComponent<Thinker>();
if (c) c->ThinkAboutDoingIt();
}

int main()
{
Entity e;
e.AddComponent(boost::shared_ptr<BaseObj>(new BaseObj));
e.AddComponent(boost::shared_ptr<D1Obj>(new D1Obj));
e.AddComponent(boost::shared_ptr<Thinker>(new Thinker));

boost::shared_ptr<D1Obj> c = e.FindComponent<D1Obj>();
if (c) c->DoSomething();

DoItToABase(e);
ThinkAboutIt(e);
}



There's definitely an ambiguity case with requesting a BaseObj when multiple objects are in the list derived of BaseObj. But that would be well documented and understood. In other words the inheritance graph i made is not realistic. If it were say a base physics object, the derived rigidbody, character and vehicle classes. Only one of those would be in the component list at any time.

Say you included the character physics object. Then, requesting a physics base would return the base pointer to the character physics object. Requesting the character would return the character physics object itself.

Share this post


Link to post
Share on other sites
What kinds of Specifications are there? Why does Aircraft::setSpec expect a FighterSpec* specifically, instead of any kind of Specification*?

If the Aircraft specifically needs a FighterSpec* for some reason, why are you trying to provide it one from a container that doesn't necessarily contain them?

Share this post


Link to post
Share on other sites
Quote:
Original post by bzroom
Thanks for the sniplet. I'm currently considering the different ways to do RTTI in my system and I'm curious, how do you know that your method is considerably faster than dynamic_cast?


Once RRTI is enabled in your C++ builds...

1. It adds type data to *every* single class and struct in your game, whether you want it or not (matrices, vector3s, NetworkManager etc). Chances are, you really don't want that at all!
2. dynamic_cast uses string compares to determine if a type is compatible. Slow slow slow!

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

Participate in the game development conversation and more when you create an account on GameDev.net!

Sign me up!