3

I had a question regarding how to solve the following in C++:

So, say I have 2 items. A Sword, and a Knife.

A sword's structure looks like the following:

baseItem > Equippable (Holds events and boolean checks) > Weapon (Same) > Sword

A knife's structure looks like the following:

baseItem > Equippable > Weapon > Knife

Most of my classes and functions that will be dealing with items (Inventory, Containers, the function that creates the Item Instances) will all be of type baseItem.

How do I specify functions such as:

baseItem createItem(int index, type itemType)

in such a way that I can return or cast back up to Sword/Knife? This is especially a concern with inventories as I will need to pull items from ItemSlot which will also hold objects of type baseItem, but will need to constantly check if they are Sword, Knife, etc.

Sinistralis
  • 412
  • 3
  • 16

4 Answers4

3

You can do it with a template member function, like this:

class baseItem {
    ... // Private members
public:
    template <typename T> static T* createItem(int index);
};

Then you will be able to call this with the exact type in a type parameter, like this:

Sword *sword = basrItem::createItem<Sword>(123);

Demo on ideone.

A couple of notes:

  • You need to return by pointer, regular or "smart", to avoid object slicing.
  • You may want to move the createItem member to a separate "factory" / "registry" class
  • If you keep a registry, you need to be careful about the ownership of your objects (i.e. avoid deleting them when they are inside the registry, or when they go out of registry while being in use elsewhere).

To illustrate the last point, here is what I mean by a registry class:

class baseItemRegistry {
    map<int,unique_ptr<baseItem> > registry;
public:
    template <typename T> static T* createItem(int index) {
        map<int,baseItem*>::const_iterator iter = registry.find(index);
        if (iter != registry.end()) {
            return dynamic_cast<T*>(iter->second);
        }
        T* res = new T(index);
        registry[index] = res;
        return res;
    }
};

This would let you keep a baseItemRegistry object that you can decide to share, but you could also hide it it you need a "private" registry. static functions do not give you this flexibility.

Sergey Kalinichenko
  • 714,442
  • 84
  • 1,110
  • 1,523
  • Would multiple calls to this all return the Sword? If so, this won't work for me. – Sinistralis Jan 13 '14 at 17:08
  • @Sinistralis Multiple calls would return whatever you ask them to return. If you call `Knife *knife = basrItem::createItem(321)`, you'll get a `Knife` object back. – Sergey Kalinichenko Jan 13 '14 at 17:12
  • Alright, this sounds exactly like what I need then. My last question then would be, what is the best way in which to store the object being pointed to? You touched base on it a bit with your notes but I'm not sure I follow completely. Right now I store the items in a Vector, and that works fine. I am actually making "Instance" items, which hold the per-item data, and these are what need a location to be stored in. – Sinistralis Jan 13 '14 at 17:20
  • @Sinistralis Make a collection of `baseItem` pointers, regular or smart, and store that collection in your registry class. The way the collection is organized is up to you - it can be organized as a map by index, as a plain list, or in any other way that fits your design well. For example, you could make `list`, or `list >`, or `map >`. – Sergey Kalinichenko Jan 13 '14 at 17:23
  • Going to go ahead and mark this as an answer. If you don't mind, would you elaborate more on the registry class you are mentioning? I just picked up C++ about 5 days ago and have been rushing around learning about it. Also, am I able to perfect a switch on type entered? I have several different item factories that are too different to combine. – Sinistralis Jan 13 '14 at 17:40
  • I can use an enum for the item factories, disregard that part. – Sinistralis Jan 13 '14 at 17:54
  • I think I understand the registry thing. Would be a very convenient place to put a export/import function for server restarts. Thank you for such a thorough answer! – Sinistralis Jan 13 '14 at 18:15
0

You use dynamic cast to cast to the derived type and check whether it is of that type. If you return the type by value you run into the slicing problem: What is object slicing?

Community
  • 1
  • 1
tillaert
  • 1,797
  • 12
  • 20
0

You could return a pointer (preferably wrapped up in some kind of smart pointer) e.g. shared_ptr:

std::shared_ptr<baseItem> createItem(int index, type itemType) 
{
    // Your logic
    return std::shared_ptr<baseItem>(new Sword()); // or whatever
} // eo createItem

And later on your cast to the appropriate type using dynamic_cast.

Moo-Juice
  • 38,257
  • 10
  • 78
  • 128
0

Generally you may cast baseItem to knife or sword with dynamic_cast, but that's not a good practice. Basically all behavior that depends on type of object should be located in the virtual member-functions of your classes.

Nikita
  • 950
  • 8
  • 19
  • And I have this covered when going from Knife -> baseItem. How are you supposed to do this the other direction though when I have non-static data to worry about? (Such as durability) – Sinistralis Jan 13 '14 at 17:06