1

I have an XML that describes animals, each animal has different parameters, looks something like this:

<Animals>
 <Cat>
    </fur>
    </taillength>
 </Cat>
 <Elephant>
    </earsize>
 </Elephant>
</Animals>

And I have classes (Cat, Elephant) that Inherit from :

IAnimal
{
public: virtual IAnimal* CreateAnimal(xml) = 0 ;
}

So each class can create itself, which is great.

The problem is, that somewhere (In some factory) I must have the following code:

string name = xml->getname();
if(name.equals("cat")
{
  cat.CreateAnimal(xml);
} else if (name.equals("elephant"))
{
  elephant.CreateAnimal(xml);
}

I want to avoid that code by creating a map from String (cat/elephant) to Class that parses this types (Cat : IAnimal, Elephant : IAnimal)

And then doing the following:

map<string, IAnimal>
// populate map ... 
// ...
string name = xml->getname();
mymap[name]->CreateAnimal(xml);

The problem is to populate the map automatically, so each class will add itself at run time automatically to the map (something that can be done using static constructor in C#).

I would be happy to hear suggestions to how to do it, Thanks

OopsUser
  • 4,642
  • 7
  • 46
  • 71

4 Answers4

2

Does this suit your needs ? You use the macro REGISTER_ANIMAL(Class, xmlName) at global scope to register the factory method for a Class animal with the name xmlName.

#include <iostream>
#include <map>

struct IAnimal;

using AnimalFactory = IAnimal *(*)(std::string const&);
std::map<std::string, AnimalFactory> gFactories;

struct AnimalReg{
    AnimalReg(std::string xmlName, AnimalFactory factory) {
        gFactories.emplace(xmlName, factory);
    }
};

#define CAT_(x, y) x ## y
#define CAT(x, y) CAT_(x, y)
#define REGISTER_ANIMAL(Class, xmlName) \
    static AnimalReg const CAT(_animalReg, __COUNTER__) {xmlName, &Class::create}

struct IAnimal {};

///////////
// Usage //
///////////

struct Cat : IAnimal {    
    static IAnimal *create(std::string const &xml) {
        std::cout << "Cat\n"; return nullptr;
    }
};
REGISTER_ANIMAL(Cat, "cat");

struct Dog : IAnimal {
    static IAnimal *create(std::string const &xml) {
        std::cout << "Dog\n"; return nullptr;
    }
};
REGISTER_ANIMAL(Dog, "dog");

int main() {
    gFactories["cat"]("");
    gFactories["dog"]("");
}

Outputs :

Cat
Dog
Quentin
  • 62,093
  • 7
  • 131
  • 191
  • This could probably be done without macros, where `REGISTER_ANIMAL` was actually a template method: `register_animal(const string& name)`. – tadman Apr 27 '15 at 14:55
  • @tadman I don't think so. You can't call a method from the global scope, can you ? Maybe as part of an initializer, but then you still need to declare the initialized variable. – Quentin Apr 27 '15 at 14:58
  • You have to dance around a lot with using clever constructors. The way you've got here has been around for decades, reminds me of [Borland OWL](http://en.wikipedia.org/wiki/Object_Windows_Library), I'm just curious if there's a more C++14 way of doing it. – tadman Apr 27 '15 at 15:01
  • @tadman I've tinkered a bit with in-class initializers, but found nothing conclusive. But wouldn't it be cool. – Quentin Apr 27 '15 at 15:04
2

You could create a factory method registration system by making subclasses of IAnimal add themselves into the factory map with a string identifier and creation function. Something like this:

struct IAnimal;
//I made this a Singleton for simplicity
struct AnimalFactory
{
    //the type of a factory method
    using FactoryFunction = std::function<IAnimal*(const std::string&)>;
    //register a factory function
    bool RegisterFunction(const std::string &name, FactoryFunction f)
    {
        factoryMap.insert(std::make_pair(name,f));
        //do some error handling to see if the class is already registered, etc.
        return true;
    }

    //do the actual construction
    std::unique_ptr<IAnimal> CreateAnimal(const std::string &name, const std::string &xml)
    {
        //retrieve the factory method from the map and call it
        return std::unique_ptr<IAnimal>(factoryMap.at(name)(xml));
    }

    //singleton implementation
    static AnimalFactory &instance()
    {
        static AnimalFactory factory{};
        return factory;
    }

private:
    std::map<std::string, FactoryFunction> factoryMap;
};

Your subclasses then register themselves like this:

struct Cat : IAnimal
{
    Cat (const std::string &xml) {}
    static Cat* CreateAnimal(const std::string &xml) { return new Cat(xml); }
    static bool registered;
};
bool Cat::registered = 
   AnimalFactory::instance().RegisterFunction("cat", Cat::CreateAnimal);

struct Elephant : IAnimal
{
    Elephant (const std::string &xml) {}
    static Elephant* CreateAnimal(const std::string &xml) { return new Elephant(xml); }
    static bool registered;
};
bool Elephant::registered = 
   AnimalFactory::instance().RegisterFunction("elephant", Elephant::CreateAnimal);

Then you call the factory methods like this:

auto cat = AnimalFactory::instance().CreateAnimal("cat","hi");
auto elephant = AnimalFactory::instance().CreateAnimal("elephant","hi");

There are a number of different facets to this approach. I would highly recommend reading section 8 "Object Factories", from Andrei Alexandrescu's "Modern C++ Design" for suggestions and discussion on this.

Demo

TartanLlama
  • 63,752
  • 13
  • 157
  • 193
0

How should we give you any suggentions to add items to your map when wo do not know from where they're?

One suggestion would be just to add them by hand

myMap["cat"] = new Cat;
myMap["elephant"] = new Elephant;

or making it flexible and read that from a configuration file or a database or from the filesystem.

BTW: Maybe you should change your factory to take just an xml string then check which type it is cat/elephant and then return that type

if (name == "cat")
{
  return new Cat;
}
else if (name == "elephant")
{
  return new Elephant;
}

That would be more the strategy pattern I think.

user743414
  • 936
  • 10
  • 23
0

Use the Factory method pattern, instead of creating the map.

Disrigarding the concrete solution, you will anyway have the decision point somewhere to create the subclass instance depending on the animal name (or type).

Here is an example with Factory method:

#include <string>
#include <iostream>
#include <vector>

using std::string;
using std::vector;

class XmlReader {}; // Somewhere XmlReader defined

class IAnimal
{
public:
    static IAnimal* CreateAnimal(const XmlReader* xml);

public:
    virtual ~IAnimal() {}
    virtual string GetSound() const = 0;
};

class Cat: public IAnimal
{
    string GetSound() const { return "Miaow"; }
};

class Elephant: public IAnimal
{
    string GetSound() const { return "Rouw"; }
};

IAnimal* IAnimal::CreateAnimal(const XmlReader* xml)
{
    string name = xml->getname();
    if(name == "cat")
        return new Cat(xml);
    else if (name == "elephant")
        return new Elephant(xml);
    else
        return NULL;
}

int main()
{
    XmlReader xml("animal.xml");
    vector<IAnimal*> zoo;
    while(!xml.eof())
    {
        IAnimal* animal = IAnimal::CreateAnimal(&xml);
        std::cout << animal->GetSound() << std::endl;
        zoo.push_back(animal);
    }
}