3

I'm creating a 2D game in C++ that uses levels made out of tiles. The world class has an add(WorldObject* o) function that can both accept a tile or an entity such as an enemy. Both the Tile and the Entity class are derived from WorldObject. In every case, the object should be added to the entities list; but if it is a tile, it should also be added to the tiles list.

World.h

class World {
private:
    list<WorldObject*> content;
    list<Tile*> tiles;
public:
    void add(WorldObject*);
}

World.cpp

void World::add(WorldObject* o) {
    content.push_back(o);
    if(*o instanceof Tile) //What do I need to put here?
        tiles.push_back(o);
}

How do I check whether an object has a specific type in C++? It's nothing about typecasting and virtual functions and things like that because I don't want to invoke functions to the object at this time; I just need to add it to a separate list if it has a certain type. In Java, I can do if(instance instanceof Class). How can I do this in C++?

Rapti
  • 2,520
  • 3
  • 20
  • 23
  • 2
    `dynamic_cast<>`should well serve for your purposes, doesn't it? If you can statiaclly bind, use `static_cast<>`. – πάντα ῥεῖ Dec 22 '14 at 00:01
  • See the same question here: http://stackoverflow.com/questions/500493/c-equivalent-of-instanceof?rq=1 – Adam27X Dec 22 '14 at 00:04
  • Why not two functions, `void add(Tile*)` and `void add(Entity*)`? Is the type of the object passed to `add` not available any more? – dyp Dec 22 '14 at 00:04
  • @πάντα ῥεῖ: But as I don't wanna do a typecast. I just want to CHECK if it has the type, not CONVERT it. – Rapti Dec 22 '14 at 00:04
  • You need a cast since you have a `WorldObject*` and want a `Entity*` or a `Tile*` (to append to either list). – dyp Dec 22 '14 at 00:05
  • There's a significantly cleaner way to do this with `typeid`, I will post my answer shortly. – Colin Basnett Dec 22 '14 at 00:05
  • Can't you make add a virtual function and then define their respective meanings in the entity and tile classes? – Adam27X Dec 22 '14 at 00:07
  • @dyp see my answer, same vein I think. – Alec Teal Dec 22 '14 at 00:12
  • I suggest rather than duplicating the tiles list in the entity list that you rethink how you are doing this. I would be loathe to duplicate stored data in this fashion. – Richard Chambers Dec 22 '14 at 00:14
  • How would you do it then? My intention is to have quick access to a list with only the world's tiles and a little higher memory usage should be way better than a significantly higher CPU usage that results from going through the list and doing a check on every object it contains to see wether it is a tile. – Rapti Dec 22 '14 at 00:17
  • @RichardChambers exactly, tell the OP this should be a design pattern question and confirm my answer for him! – Alec Teal Dec 22 '14 at 00:21
  • @cmbasnett or design patterns! If you need to choose between base classes you've usually abstracted too much away! – Alec Teal Dec 22 '14 at 00:24
  • You are thinking of the implementation rather than the problem domain and you are using procedural thinking rather than object oriented thinking and laying yourself open to more and more complexity. I just recognize the anti-pattern since it is the same kind of mistake I have made in the past until I finally got enough education and reading and thinking to recognize the error when I was making it. It usually comes from crafting something incrementally and ad hoc without a good architecture and then trying to get by without refactoring and rearchitecting. – Richard Chambers Dec 22 '14 at 00:31
  • @RichardChambers *cough* http://stackoverflow.com/a/27595177/2112028 – Alec Teal Dec 22 '14 at 00:43

4 Answers4

14

A dynamic_cast will check to see if you can downcast o to Tile. If you can, it will return a valid Tile*, else it will return a null Tile*:

void World::add(WorldObject* o) {
    content.push_back(o);
    if (Tile* t = dynamic_cast<Tile*>(o)) {
        tiles.push_back(t);
    }
}
Barry
  • 286,269
  • 29
  • 621
  • 977
5

You can only infer the dynamic type of an object at run-time if the object is polymorphic, ie has at least one virtual function member. A virtual destructor will also do for this matter. (Actually, talking about a “dynamic type” doesn't make much sense if nothing is virtual anyway.)

Then you can try a dynamic_cast as an instanceof equivalence. Where you would have written

void f(Base base) {
    if (base instanceof Derived) {
        Derived derived = (Derived) base;
        // use derived
    }
}

in Java, you would write

void
f(Base& base)
{
  if (Derived * derived_ptr = dynamic_cast<Derived *>(&base))
    {
      // use derived_ptr
    }
}

in C++. A dynamic_cast to a pointer type will simply return a nullptr (which evaluates to false) if the object is not of the right type so it is usually seen inside an if. A dynamic_cast to a reference, however, would throw an exception so you should only use it if you are sure that the object actually is of that type.

I should add that the C++ example will only be valid if Derived is actually derived from Base. Otherwise the check would have been rather pointless anyway. Note however that you cannot restore the type of an object through a void * pointer. It makes sense if you think about it because that void * might actually point to a random collection of bytes that has nothing whatsoever to extract a type from.

5gon12eder
  • 24,280
  • 5
  • 45
  • 92
2

You can do this:

if(dynamic_cast<Tile*>(o))

This works because dynamic_cast on a pointer returns null if the type is not compatible. And of course, a null pointer is "falsy", so the check will fail. If o is in fact a Tile, a non-null Tile* will result, and the check will succeed.

John Zwinck
  • 239,568
  • 38
  • 324
  • 436
1

This should be an answer, but it may not be /the/ answer. What you're doing is awful. You will have something like:

void addObject(Object* ob) {
    if(ob->isA()) { doA(ob); }
    if(ob->isB()) { doB(ob); }
    if(ob->isC()) { doC(ob); }
    genericObjectStuff(ob);
}

This is an "antipattern" because it is bad, it means for each derived you have (in your case Tile is CURRENTLY the only one) you'll have to come back to this function and add any special behaviour.

There are two fixes (that depend on what the objects in play are like)

1) Virtual functions:

void addObject(Object* ob) {
    ob->doSpecificThingYouNeedToDo(this);
    genericObjectStuff(ob);
}

Where doSpecificThingYouNeedToDo will doA(this); inside if it is an A, do B if it is a B.... thus you write what it does when it is added where you define the class! Much better!

2) You've abstracted away too much!

If I give you a DrivableThing you know you can do whatever it means to be a DrivableThing to it, you are not given that it is a Truck say (which IS A DrivableThing) so you cannot loadCargo on the drivable thing because it might not be a truck!

So a function that loads cargo can only accept Trucks, or some other type that is drivable and loadable. It'd be wrong to have a generic function that loaded trucks, but also put pizzas on the back of motorbikes (Delivery task also!)

This is the opposite of 1, rather than adding the functionality to the derived classes, you want different addObjects that accept the specific object only. Because they require this information, so you currently have:

void loadUpCargo(DrivableThing* thing) {
    if(thing->isPizzaBike()) { thing->addPizzas(whatever); }
    if(thing->isTruck()) { thing->addCrates(whatever); }
    thing->driveOff(); //we can always drive off DrivableThings
} 

You can change this to:

void loadUpCargo(Truck* truck) {
    truck->addCrates(whatever);
    truck->driveOff(); 
}
void loadUpCargo(PizzaBike* bike) {
    bike->addPizza(whatever);
    bike->driveOff();
}

Much better!

The classic example of this is "Animal class has an Eat method" "Bananas are Eatable, a Monkey is an Animal" but having an Animal and an Eatable wont stop you feeding Grass to a Monkey because you've abstracted the information away!

Alec Teal
  • 5,770
  • 3
  • 23
  • 50
  • 1
    I think you got my intention entirely wrong. I do know what virtual functions are and when to use them. I just want to have a list with everything the level contains and an additional list with just the tiles for faster collision checking. – Rapti Dec 22 '14 at 00:14
  • @Rapti this isn't about virtual functions at all. It's object patterns. – Alec Teal Dec 22 '14 at 00:20
  • 1
    Sorting things by type for performance reasons is actually one of the rare valid uses of run-time type inference (RTII) so the OP's desire does seem legitimate to me. If you can already sort at the place where to objects are created (so there type is known) then that's of course even better but I wouldn't break up an otherwise clean API to expose this optimization / implementation detail just to avoid a single use of RTTI deeply inside somewhere it belongs. – 5gon12eder Dec 22 '14 at 00:27
  • I added that comment when you hadn't added 2) yet. But still, my code it doesn't match your animal example because absolutely any WorldObject can be added to the world; it's not a monkey just eating bananas. I just want to do one extra task if it has a certain type (add it to the tile list if it is a tile). Creating a separate add(Tile* t) function wouldn't make much sense because if the tile is passed on as a WorldObject*, it wouldn't be added to the tiles list. – Rapti Dec 22 '14 at 00:28
  • @Rapti here the animal is your tiles list, and you are trying to make sure only Bananas get added to it - QED – Alec Teal Dec 22 '14 at 00:42
  • And if I create an `addTile(Tile* t)` function, what prevents someone from passing a `WorldObject*` that actually points to a tile to the `addTile(WorldObject* o)` function, resulting in the tile not being added to the tiles list? – Rapti Dec 22 '14 at 00:45
  • @5gon12eder either way there's a vtable lookup, and you'd be relying on compiler optimisations with dynamic_cast (think about a dynamic cast, it has to get the typeid of the two objects, then see if it's a valid base, which is a graph search, at best a tree....) with a vtable there's much less than that, if the compiler does it at all. With the second one the overhead is compile time due to overloading, __MOST IMPORTANT POINT__ Optimising early is the root of all evil. Also how many tiles would he have to add and how little work would they do if this optimisation got his FPS up from 30 to 60? – Alec Teal Dec 22 '14 at 00:46
  • @Rapti don't have an `addTile(WorldObject*)` now I know you typoed for just `add(WorldObject*)`, if the caller doesn't know if it's a tile or a worldobject then you want the first method. Callbacks are one of the main uses of the pattern (have the added WorldObject that is actually a Tile callback the World and add itself to the tiles list, a method called `onAddedToWorld(World*)` would be perfect. – Alec Teal Dec 22 '14 at 00:49
  • Oh, strangely I can no longer fix the typo. But whatever, so you're telling me to separate by using virtual functions? Does this not mean I would be calling a world function with my object that does a call to my object so it adds itself to the world rather than adding it directly to the world? I think that would be a design pattern much worse than you accuse mine to be. – Rapti Dec 22 '14 at 00:56
  • @Rapti read 1 again. Also I'm done with this, I can't force help on you and I've wasted enough time. Good luck - you'll need it. – Alec Teal Dec 22 '14 at 00:58