2

I'm building a c++ framework that can be extended by adding new class

I would like to find a way for simplifying the extension of new classes.

my current code look like:

class Base {
public:
    virtual void doxxx() {...}
};

class X: public Base {
public:
    static bool isMe(int i) { return i == 1; }
};

class Y: public Base {
public:
    static bool isMe(int i) { return i == 2; }
};

class Factory {
public:
    static std::unique_ptr<Base> getObject(int i) {
        if (X::isMe(i)) return std::make_unique<X>();
        if (Y::isMe(i)) return std::make_unique<Y>();

        throw ....
    }
};

Also for every new class a new if-statement must be added.

Now I would like to find a way to rewrite my Factory class (using meta programming) that adding a new class can be done by calling an add method and the factory class looks like following pseudo code:

class Factory
{
public:
    static std::unique_ptr<Base> getObject(int i) {
        for X in classNames:
            if (X::isMe(i)) return std::make_unique<X>();

        throw ....
    }

    static void add() {...}

    static classNames[];...
};

Factory::add(X);
Factory::add(Y);

. .

is something like that possible? Many thanks in advance

Jarod42
  • 203,559
  • 14
  • 181
  • 302
Mauri
  • 1,185
  • 1
  • 13
  • 21
  • Can you explain why you would want to do something so terrible? – Aluan Haddad Feb 22 '18 at 11:33
  • @AluanHaddad because the getObject doing more than the if and many other colleagues can add new classes. So I would like to find a way to minimize the code other collegues must write – Mauri Feb 22 '18 at 11:37
  • 1
    Do you mean `if (Y::isMe(i)) return Y();` instead? Also, shouldn't `getObject` return something like `I *` or `std::shared_ptr`/`std::unique_ptr` – jdehesa Feb 22 '18 at 11:44
  • @jdehesa exactly – Mauri Feb 22 '18 at 11:48
  • @Jarod42 the 2 X was a typo – Mauri Feb 22 '18 at 11:52
  • Also check out this related (or dupe?) question [automatic registration of object creator function with a macro](https://stackoverflow.com/questions/6137706/automatic-registration-of-object-creator-function-with-a-macro) – jdehesa Feb 22 '18 at 12:06

5 Answers5

6

You might do something like the following:

template <typename ... Ts>
class Factory {
public:
    static std::unique_ptr<Base> createObject(int i) {
        if (i < sizeof...(Ts)) {
            static const std::function<std::unique_ptr<Base>()> fs[] = {
                [](){ return std::make_unique<Ts>();}...
            };
            return fs[i]();
        }
        throw std::runtime_error("Invalid arg");
    }

};

Usage would be:

using MyFactory = Factory<X, Y /*, ...*/>;

auto x = MyFactory::createObject(0);
auto y = MyFactory::createObject(1);

If you want runtime registration, you might do instead:

class Factory {
public:
    static std::unique_ptr<Base> createObject(int i) {
        auto it = builders.find(i);
        if (it == builders.end()) {
            throw std::runtime_error("Invalid arg");
        }
        return it->second();
    }

template <typename T>
void Register()
{
    builders.emplace(T::Id, [](){ return std::make_unique<T>();});
}

private:
    std::map<int, std::function<std::unique_ptr<Base>()>> builders;
};

Usage would be:

class X: public Base {
public:
    static constexpr int id = 1;
};

class Y: public Base {
public:
    static constexpr int id = 2;
};

and

Factory factory;
factory.register<X>();
factory.register<Y>();

auto x = factory.createObject(1);
auto y = factory.createObject(Y::id);
Jarod42
  • 203,559
  • 14
  • 181
  • 302
1

You can just have a template function:

#include <memory>

class Base {
public:
    virtual void doxxx() { /* ... */ };
};

template<int i>
std::unique_ptr<Base> getObject();
#define REGISTER_CLASS(CLS, ID) \
template<> \
std::unique_ptr<Base> getObject<ID>() { return std::unique_ptr<Base>(new CLS()); }

class X1: public Base {
public:
   // ...
};
REGISTER_CLASS(X1, 1)

class X2: public Base {
public:
    // ...
};
REGISTER_CLASS(X2, 2)

int main()
{
  auto obj1 = getObject<1>();  // Makes X1
  auto obj2 = getObject<2>();  // Makes X2
  return 0;
}

However this only allows for class id values known at compile time.

jdehesa
  • 58,456
  • 7
  • 77
  • 121
1

You could do something like this:

#include <type_traits>
#include <utility>
#include <memory>
#include <functional>
#include <stdexcept>

class I {
public:
    virtual ~I(); // Always give a polymorphic class a virtual dtor!
    virtual void doxxx();
};

enum class I_Key {
    X = 1,
    Y
    /*...*/
};

struct I_Key_Order {
    bool operator()(I_Key k1, I_Key k2) const
    {
        using U = std::underlying_type_t<I_Key>;
        return static_cast<U>(k1) < static_cast<U>(k2);
    }
};

class I_Factory {
public:
    using Generator = std::function<std::unique_ptr<I>()>;
    static std::unique_ptr<I> getObject(I_Key key) const;
    static void add(I_Key key, Generator gen);

    template <class T>
    class AutoRegister {
    public:
        AutoRegister() {
            auto generator = []() -> std::unique_ptr<I>
                { return std::make_unique<T>(); };
            add(T::class_key, std::move(generator));
        }

        AutoRegister(const AutoRegister&) = delete;
        AutoRegister& operator=(const AutoRegister&) = delete;
    };

private:
    using GenMapT = std::map<I_Key, Generator, I_Key_Order>;
    static GenMapT& m_generators();
};

inline std::unique_ptr<I> I_Factory::getObject(I_Key key)
{
    auto& gen_map = m_generators();
    auto iter = gen_map.find(key);
    if (iter != gen_map.end())
        return iter->second();
    throw std::invalid_argument("unknown I_Factory key");
}

inline void I_Factory::add(I_Key key, Generator gen)
{
    m_generators().emplace(key, std::move(gen));
}

I_Factory::GenMapT& I_Factory::m_generators()
{
    static GenMapT generators;
    return generators;
}

class X : public I {
public:
    static constexpr I_Key class_key = I_Key::X;
    static const I_Factory::AutoRegister<X> _reg;
};

class Y : public I {
public:
    static constexpr I_Key class_key = I_Key::Y;
    static const I_Factory::AutoRegister<Y> _reg;
};

Note in addition to an I_Factory::add() function, I've set up a second often easier way to have a generator for class C added: define an object of type I_Factory::AutoRegister<C>, as long as C::class_key is a valid enumerator. If the object is a static class member, the adding will happen essentially at the start of the program. This will only work if the class has a public default constructor, so the add function might still be used if something different is necessary.

aschepler
  • 70,891
  • 9
  • 107
  • 161
0

Here is one version with an add function and a map to store the different types.

class Base {
public:
    virtual void doxxx() {...}
};

class X: public Base {
public:
    static int isMe = 1;
};

class Y: public Base {
public:
    static int isMe = 2;
};

class Factory {
public:
    static std::unique_ptr<Base> getObject(int i) {
        if (classID.find(i) != classID.end())
            return classID[i]();

        throw ....
    }

    template <typename C>
    static void add() {
        classID[C::isMe] = [](){ return std::make_unique<C>(); };
    }

    static std::map<int, std::function<std::unique_ptr<Base>()>> classID;
};

// Called like this
Factory::add<X>();
Factory::add<Y>();
super
  • 12,335
  • 2
  • 19
  • 29
0

A C++14 solution.

Required headers:

#include <memory>
#include <iostream>

A class to throw when the index cannot be found

class ClassNotIndexed{};

A helper class to help create a binding between the index and the requested type:

template< typename ClassType, typename IndexType, IndexType idx >
class ClassIndexer
{};

The primary template for out factory class

template <typename... >
class ClassSelector;

Base case for factory class. When index is found then create the unique_pointer, otherwise throw an exception:

template<typename BaseClass,
            typename ClassType, typename IndexType, IndexType idx,  
            template<typename,typename,IndexType> typename T>
class ClassSelector<BaseClass, T<ClassType,IndexType, idx>>
{
public:
    constexpr static
    std::unique_ptr<BaseClass> getObject(IndexType i)
    {
        if (i == idx)
            return std::make_unique<ClassType>();
        else
            throw ClassNotIndexed();
    }
};

Variadic template which takes many Indexed classes. If the index of the first class is a match, then make_unique_pointer otherwise check the next indexed class:

template<   typename BaseClass,
            typename ClassType, typename IndexType, IndexType idx,  
            template< typename , typename, IndexType > typename T,
            typename... Ts >
class ClassSelector<BaseClass, T<ClassType,IndexType, idx>, Ts... > : ClassSelector< BaseClass, Ts ... >
{
    using Base = ClassSelector< BaseClass, Ts ... >;

public:
    constexpr static
    std::unique_ptr<BaseClass> getObject(IndexType i)
    {
        if (i == idx)
            return std::make_unique<ClassType>();
        else
            return Base::getObject( i );
    }
};

Example:

class Base
{
public:
    virtual void doxxx() {}
};

class X : public Base
{
public:
    //static bool isMe( int i ) { return i == 1; }
    void doxxx() override {std::cout << "I'm X !!\n";}
};

class Y : public Base
{
public:
    //static bool isMe( int i ) { return i == 2; }
    void doxxx() override {std::cout << "I'm Y !!\n";}
};


int main()
{
    using Factory = ClassSelector< 
                        Base,
                        ClassIndexer< X, int, 1 >, 
                        ClassIndexer< Y, int, 2 > 
                    >;


    Factory::getObject( 1 )->doxxx();
    Factory::getObject( 2 )->doxxx();

    int choose;

    std::cin >> choose;

    Factory::getObject( choose )->doxxx();

    return 0;
}
Robert Andrzejuk
  • 5,076
  • 2
  • 22
  • 31