1

What I want to do: I load data from a XML file (this goes fine). The XML tells the program to create class instances (from specific different classes, like Car or Plane) which then will exist within the program. These class instance objects are subclasses of an overarching Object base class.

The XML file stores what type of object needs to be created in the form of a number, from which the program will determine which class of object to create.

I could use a switch statement and enumerate all types of objects, but this is incredibly taxing on performance when adding loads of instances of objects. So instead, I want a mapping from char[2] to the required class. (Note: to the class type itself, not an instance of the class.)

For example, if the XML attribute says 'type=0x01', then an object from the class Car will be made, while if 'type=0x02' then a Plane object will be made. (Both are kinds of Object)

In this way, I want to use a constant map to get this done. I want to write something like

map<char[2],class> ObjectsList = {
     {0x01,Car},
     {0x02,Plane}
     //etc, etc.
}

...
// while loading data from xml file on which objects need to get created,
// an object-tag gives data from 'type' attribute, and the program stores
// it in 'char[2] type';
char[2] type = getattributestufffromxml("type");
ObjectsList[type] newObject = memoryallocator.allocate(sizeof(ObjectsList[type]);
newObject.Init(); //inherited function from Object

The idea of this is to create a faster approach rather than sticking with a switch-statement, which is awful when creating hundreds of objects.

What do I need to do to get from the above to something that's valid C++? I do not know how I can store class types in a map. (I get compiler errors such as, 'parameter 2/4 are invalid')

πάντα ῥεῖ
  • 1
  • 13
  • 116
  • 190
Generaal
  • 13
  • 4
  • 2
    *Anyone got any idea how I can fix this?* What is wrong with it? – NathanOliver May 19 '16 at 15:57
  • I added a bit of extra explanation for clarity. The problem is; I want to store the class type as a reference in the map, so it's easily referenced. – Generaal May 19 '16 at 16:00
  • 1
    Are you *intentionally* [slicing your objects](https://stackoverflow.com/questions/274626/what-is-object-slicing) in that map? – WhozCraig May 19 '16 at 16:01
  • 1
    @Generaal Looks more like a case to use `std::map` for me. – πάντα ῥεῖ May 19 '16 at 16:01
  • @WhozCraig: no, that is not my intention. – Generaal May 19 '16 at 16:04
  • @πάνταῥεῖ: in the map I am not attempting to store the instances of the objects, but rather the object types themselves as a reference. – Generaal May 19 '16 at 16:08
  • @Generaal Yeah, I've got that. Wrote an appropriate answer. – πάντα ῥεῖ May 19 '16 at 16:28
  • Have you proved that the big `switch` is slower than a `map` lookup? What if you nest a switch on each char: `switch (type[0]) { case '0': switch (type[1]) { case '0': return new Car; case '1': return new Plane; /* more 0x cases */ } case '1': switch (type[1]) { /* etc */ } }` – Toby Speight May 19 '16 at 16:51

2 Answers2

3

I do not know how I can store class types in a map. (I constantly get 'parameter 2/4 are invalid')

Well, a char[2] array can't be used as the std::map::key_type since it doesn't fulfill the necessary constraints for the Compare concept.
From your initializer list it looks you want a uint8_t as key value anyways.

Also you can't store types as values in your map.

Should it be you meant to use function pointers for constructors, that's not possible since constructor and destructor functions are special beasts (no return type not even void), and you can't reference to them with a function pointer.


I think what you actually need is a std::map storing factory functions corresponding to a enum like:

enum class VehicleTypes : uint8_t {
    CarType = 0x01 ,
    PlaneType = 0x02 ,
}; 

struct Vehicle {
     virtual ~Vehicle() {}
     virtual void Init() = 0;
};

typedef std::function<Vehicle*()> CreateVehicleFn;

class Car : public Vehicle {
public:
     virtual void Init() {
         // do anything necessary to init a Car
     }
};

class Plane : public Vehicle {
public:
     virtual void Init() {
         // do anything necessary to init a Plane
};

std::map<VehicleTypes,CreateVehicleFn> CreatorFnList = 
     { { VehicleTypes::CarType, []() { return new Car(); } } ,
       { VehicleTypes::PlaneType, []() { return new Plane(); } }
     };

The latter map initialization list isn't much more code to write than you provided in your (pseudo-) code example.
If you want to get rid of the boiler plate stuff, and think it's worth to obfuscate your code, you can still use a macro:

#define Create(classtype) []() { return new classtype(); }

std::map<VehicleTypes,CreateVehicleFn> CreatorFnList = 
     { { VehicleTypes::CarType, Create(Car) } ,
       { VehicleTypes::PlaneType, Create(Plane) }
     };

You can use it then later to create new instances depending on the key parameter:

Plane* plane = dynamic_cast<Plane*>(CreateVehicleFn[VehicleTypes::PlaneType]());
if(plane) {
    plane->Init();
}

To provide clear ownership semantics even consider to use a std::unique_ptr<Vehicle> to pass around the new instances from factory to client:

typedef std::function<std::unique_ptr<Vehicle>()> CreateVehicleFn;

std::map<VehicleTypes,CreateVehicleFn> CreatorFnList = 
     { { VehicleTypes::CarType, []() { return make_unique<Car>(); } } ,
       { VehicleTypes::PlaneType, []() { return make_unique<Plane>(); } }
     };

The syntax for usage is much the same as for my 1st sample:

std::unique_ptr<Plane> plane = CreateVehicleFn[VehicleTypes::PlaneType]();
if(plane.get()) {
    plane->Init();
}
Community
  • 1
  • 1
πάντα ῥεῖ
  • 1
  • 13
  • 116
  • 190
1

In C++, a class is a (compile-type) definition, not a (run-time) data object. So you can't store a class.

However, you can declare a Factory type, and store instances of the factory.

For example:

class Factory
{
     virtual std::unique_ptr<Object> createInstance(std::string description);
}

class CarFactory : public Factory
{
     std::unique_ptr<Object> createInstance(std::string description) override;
}
class PlaneFactory : public Factory
{
     std::unique_ptr<Object> createInstance(std::string description) override;
}

We then store factories:

std::map<const char[2],Factory*> factories = {
     {"00", new CarFactory},
     {"01", new PlaneFactory},
     //...
}

You might be able to make a generic factory:

template<typename T>
class VehicleFactory : public Factory
{
     std::unique_ptr<Object> createInstance(std::string description) override
     { return std::make_unique<T>(description); }
}

std::map<int,Factory*> factories = {
     {"00", new VehicleFactory<Car>},
     {"01", new VehicleFactory<Plane>},
     //...
}

Once we have a factory, we can use it:

std::unique_ptr<Object> createVehicle(const char type[2], std::string description)
{
    // error handling is an exercise for the reader
    return factories[type]->createInstance(description);
}
Toby Speight
  • 27,591
  • 48
  • 66
  • 103
  • 1
    Why not just wait and post a complete answer? – NathanOliver May 19 '16 at 16:14
  • @Toby Bit of _old school style_. – πάντα ῥεῖ May 19 '16 at 16:27
  • @Nathan - main reason is because I came here from reviewing Low Quality questions, and it already had three close votes. I wanted to show that a good answer existed despite the unclear question, and to not lose too much work if the question was closed. A secondary aim is to save the time of other answerers thinking of writing something very similar (although I wouldn't normally post a half-answer for that reason alone). – Toby Speight May 19 '16 at 16:29
  • @πάντα - yes, I know; I was hoping this would be clearer for the questioner. I've upvoted your answer, and I think that both answers together give a pretty good picture. – Toby Speight May 19 '16 at 16:32
  • @TobySpeight _"and I think that both answers together give a pretty good picture"_ Sure. – πάντα ῥεῖ May 19 '16 at 16:33