5

I am trying to create some classes which only contain data members (no functions) but I would like them to be polymorphic - by which I mean I will be passing around the objects by a pointer to the base class, and I need the ability to dynamic_cast them to a specific derived type (and have the resulting value be NULL if the instance is not of the given type.)

By way of example, I have an item:

struct Item {
    int x, y;
}

I also have an item which moves, and another which contains text:

struct MovingItem: virtual public Item {
    int speedX, speedY;
}

struct TextItem: virtual public Item {
    std::string text;
}

Presumably I have to use virtual inheritance above, because I also want an item that moves and has text, but I only want the one set of coordinates from the top-level Item:

struct MovingTextItem: virtual public MovingItem, virtual public TextItem {
}

These can all be defined correctly, but when I try to dynamic_cast an Item * to see what type it is, my compiler complains that the source types aren't polymorphic.

void example(Item *i) {
    MovingTextItem *mti = dynamic_cast<MovingTextItem *>(i);  // error!
}

This would work if I reimplemented the whole thing using virtual functions instead of data members, but this seems like a waste as I never need to override anything.

The only workaround I can think of is to add a type member to the base Item class, and check that instead of using dynamic_cast, and if it's of the correct type then use static_cast instead. (The drawback there being I have to know about all object types somewhere so the assigned type values don't conflict.)

Is this the best solution, or is there another way?

Clarification

For arguments' sake, imagine I am writing each object type to a file. MovingItem goes to one file, TextItem goes to a different file, and MovingTextItem goes to both files. So having a base class which implements every interface won't work, unless I can somehow tell which interfaces are in use so they get written to the correct files.

Malvineous
  • 25,144
  • 16
  • 116
  • 151
  • I have to agree with others. Use a compositional hierarchy, since the composition of your objects is the only part that changes. The only reason to use an inheritance hierarchy is when you need to use a derived class as if it's a base class, or when you otherwise need functionality that can't be achieved with composition. – derpface Aug 18 '12 at 11:55
  • @uberwulu: Even this can be achieved with interfaces, they can be derived from each other, too. There must be an example where inheritance is *the* way to go, but it has slipped my mind... – krlmlr Aug 18 '12 at 12:06
  • To your clarification: There is the *don't ask, tell* rule -- perhaps the implementations should themselves choose to which file they write their data. – krlmlr Aug 20 '12 at 19:50
  • @user946850: In this case the implementations don't know what format to write the data. Maybe it's a set of text files, maybe it's a bunch of database tables. The caller has to detect the object type and write it out accordingly. Having one class for files and one for database tables is easier, in this case, than subclassing each `Item` subtype for each storage type. – Malvineous Aug 23 '12 at 01:42

4 Answers4

6

If you add a virtual function, the classes will get a vtable so that dynamic_cast works. A no-op virtual destructor will do. (As Torsten points out, this even might be necessary in your case as you have non-POD members.)

However, from my own experience (and I have been resisting this for years!) I would strongly advise against inheritance in this particular case. Use aggregation instead. Prefer composition over inheritance? You may have to create a couple of forwarders, but the flexibility gained by this pays off (in terms of being able to later change implementations without influencing the whole system).

Think of an item that has a speed and, in addition, a text and a position, instead of being an item that somehow has speed and text and by some magic inherits the position. What if you later want an item that has speed but no known position?

To achieve your results in an aggregation scenario, define "interfaces" that constitute the contract concerning a particular feature. No need for virtual inheritance, too: The "interfaces" are just classes with only pure virtual functions. See also: What does it mean to "program to an interface"? (Another important rule if you want maintainable software.)

Example

You could define four "interfaces" (classes with only pure virtual functions): Position, Speed, Text and Item. Item inherits from the three former and defines no functions itself. You provide "reference implementations" for the first three interfaces. The "reference impl." for Item has three data members (the ref. impl. of the first three interfaces) and forwards to these implementations. Now you can use e.g. Position wherever you need something that has a position, without having to know what exactly it is. dynamic_cast works, too.

You could also define an interface ItemBase with no virtual methods and have Position, Speed and Text inherit from this interface. This will allow you to access all featurettes through the same base type and dynamically test for the availability of a subinterface. Still no need for virtual inheritance, I think...

Community
  • 1
  • 1
krlmlr
  • 25,056
  • 14
  • 120
  • 217
  • Thanks for the detailed response! I am a little uncertain how I could use aggregation in this case without using inheritance, but still have a `std::vector` containing different item types. Are you able to suggest a short example? – Malvineous Aug 18 '12 at 12:17
  • If I understand correctly, this would mean every `Item` has a `Position`, `Speed`, etc. But I specifically need a type that does *not* have a speed, as the presence or lack of these properties is used to control other things (in this case the type of data written to a file.) I'm also not sure what you mean by making `Position` etc. interface classes - doesn't this mean I'll have to replace my data members with virtual functions? I want to avoid this if possible, because I don't need to override them, they are just passive variables for storing data. – Malvineous Aug 19 '12 at 03:46
  • @Malvineous: You can create an `ItemWithoutSpeed` by the same token. Or just make the `Speed` interface available not through `dynamic_cast`, but through an accessor like `Speed* GetSpeed()` which returns `NULL` if there's no speed. (Also saves you from writing these stupid forwarders.) Depends on if the logic of items with speed is equal or different from that of items without speed: Same logic would imply only one `Item` class, different logic -> `Item` and `ItemWithoutSpeed`. If you are unhappy with C++-style properties, you can define a low-level accessor `int& x() { return this->x; }`. – krlmlr Aug 19 '12 at 08:53
2

You could and maybe even should add a virtual destructor to your base class. Then it is possible to use the dynamic_cast. You also might have a look at the visitor pattern. That pattern can be very useful, if you have a quit small and rarely changing set of data types and have a richer set of operations on that data types.

Torsten Robitzki
  • 3,041
  • 1
  • 21
  • 35
2

Let's say you could do this. You have MovingItem with a velocity. You have TextItem with a string. And you'll probably even have MovingTextItem with a string and velocity, which uses multiple virtual inheritance from MovingItem and TextItem.

And you stick all of these in a std::vector<Item*>. OK, fine.

How do you use that?

Does every piece of code that takes an Item* need to employ a dynamic_cast to the actual type it needs (thus exiting if it is not the proper type)? Or do the callers of those functions do the dynamic_cast? Either way, that's a lot of dynamic_casting just to use these values.

But that's minor. The real problem is this: how do you delete them?

See, unless Item has a virtual destructor, calling delete on any Item* will be extremely bad. Without a virtual destructor, C++ has no way of calling the destructors of any derived classes. So you would somehow need to get the actual type of any Item* item.

And that's going to require a big sequence of dynamic_cast operations. And every time you add a new derived Item class, you're going to need to add another dynamic_cast check to the list.

Or you could just give Item a virtual destructor and let C++ do its job. That way, you don't need a series of dynamic_casts just to find the actual object type to delete it. Even better, since it has a virtual destructor, Item will be a virtual type and C++ will let you do all of those dynamic_casts that you seem to want to do.

Granted, the fact that you're using dynamic_cast this much is an immediate red flag that your design is in desperate need of revision.

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
  • Yes I don't like having to use dynamic_cast so much, but I'm not sure of a better way. I didn't think `delete` was a problem, as I was using smart pointers, but now that you mention it even storing them in boost::shared_ptr means it will eventually call `delete` on the `Item *` instead. I guess you can't really do this sort of thing in C++ unless you have at least one virtual function :-( – Malvineous Aug 23 '12 at 01:48
0

Just for the record, I ended up redesigning this so that there is a single Item type, with flags to indicate which fields are valid:

enum ItemType {HasSpeed = 1, HasText = 2};
struct Item {
    int type;
    int x, y;

    int speedX, speedY;
    std::string text;
}

Then I just set type = HasSpeed | HasText if both the speed and text fields are valid. A bit old school perhaps, but it is much simpler!

Malvineous
  • 25,144
  • 16
  • 116
  • 151