Unforgettable Factory and multiple levels of inheritance and hierachy

Viewed 57

I've been experimenting with the factory implementation described here:

http://www.nirfriedman.com/2018/04/29/unforgettable-factory/

The example uses "Animal" as a base and "Dog" and "Cat" as the derived classes that are registered with the factory. But now suppose I wanted to add a "Lion" class that derives from Cat... how can I derive from Cat while still registering with the Animal factory? Further, I would like to be able to create a "Cat" factory as well that would let me create object of type "Cat" instead of "Animal". Can this example be adapted to do this?

Here is some code based off the original post that shows what I'm trying to do.

#include <functional>
#include <memory>
#include <exception>
#include <iostream>
#include <string>
#include <map>
#include <unordered_map>
#include <cstdlib>
#include <cxxabi.h>

template<typename BaseT, typename ...Args>
class Factory
{
    public:

        friend BaseT;

        template<class ...T>
        static std::shared_ptr<BaseT> create(const std::string& name, T&&... args)
        {
            try
            {
                return types().at(name)(std::forward<T>(args)...);
            }
            catch(std::out_of_range e)
            {
                throw std::runtime_error("type \"" + name + "\" is not registered with the factory");
            };
        };


        template<typename T>
        struct Registrar : BaseT
        {

            public:

                friend T;

                static bool register_type()
                {
                    auto demangle = [](const char* name) -> std::string
                        {
                            int status = -4;
                            std::unique_ptr<char, void (*)(void *)> res{abi::__cxa_demangle(name, NULL, NULL, &status), free};
                            return (status == 0) ? res.get() : name;
                        };
                    const auto name = demangle(typeid(T).name());
                    std::cout << "registering " << name << "\n";
                    Factory::types()[name] = [](Args... args) -> std::shared_ptr<BaseT>
                    {
                        return std::make_shared<T>(std::forward<Args>(args)...);
                    };
                    return true;
                };

                static inline bool registered = register_type();

            private:

                Registrar() : BaseT() { (void) registered; };
        };

    private:

        using FunctionT = std::function< std::shared_ptr<BaseT>(Args...)>;

        static auto& types()
        {
            static std::unordered_map<std::string, FunctionT>  m_types;
            return m_types;
        };
};


struct Animal : Factory<Animal>
{
    virtual void speak() = 0;
    virtual ~Animal() = default;
};



// How can I create a Cat factory at this level in the hierarchy?
class Cat : public Animal::Registrar<Cat>
{
    public:

        Cat() {};

        virtual ~Cat() = default;

        virtual void speak() { std::cout << "Meow!" << "\n"; };
};

// I would like to register Lion with the Animal factory.
class Lion : public Cat 
{
    public:

        Lion() {};

        void speak() { std::cout << "Roar!" << "\n"; };

};


int main(int argc, char** argv)
{
    auto a = Animal::create("Cat");
    a->speak();

    auto lion = Cat::create("Lion"); // this doesn't work
    lion->speak();
    return 0;
};
1 Answers

I ended up tweaking the factory class slightly by remove inheritance from Base in the Registrar class. I changed this:

template <class T> struct Registrar : Base {

to this:

template <class T> struct Registrar

Going back to my example above I now use the factory like so:

struct Animal
{
    virtual void speak() = 0;
    virtual ~Animal() = default;
};

using AnimalFactory = Factory<Animal>;


struct Cat : public Animal, public AnimalFactory::Registrar<Cat>
{
    <snip>
};

struct Lion : public Cat, public AnimalFactory::Registrar<Lion>
{
    <snip>
};

The main difference is that now each class has to derive from an Animal class or sub-class as well as the AnimalFactory::Registrar type. It's a little more verbose but I now have the ability to use the factory with multiple levels of inheritance.

Related