Estoy construyendo un marco de trabajo de C ++ que se puede ampliar agregando una nueva clase

Me gustaría encontrar una manera de simplificar la extensión de nuevas clases.

Mi código actual se ve así:

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 ....
    }
};

También para cada nueva clase se debe agregar una nueva instrucción if.

Ahora me gustaría encontrar una manera de reescribir mi clase Factory (usando meta programación) que se puede agregar una nueva clase llamando a un método add y la clase factory se parece al siguiente pseudocódigo:

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);

. .

¿Es posible algo así? Muchas gracias de antemano

2
Mauri 22 feb. 2018 a las 14:32

5 respuestas

La mejor respuesta

Puede hacer algo como lo siguiente:

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");
    }

};

El uso sería:

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

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

Si desea el registro en tiempo de ejecución, puede hacer lo siguiente:

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;
};

El uso sería:

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

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

Y

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

auto x = factory.createObject(1);
auto y = factory.createObject(Y::id);
6
Jarod42 22 feb. 2018 a las 12:45

Podrías hacer algo como esto:

#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;
};

Tenga en cuenta que además de una función I_Factory::add(), he configurado una segunda forma a menudo más fácil de agregar un generador para la clase C: defina un objeto de tipo I_Factory::AutoRegister<C>, siempre que { {X3}} es un enumerador válido. Si el objeto es un miembro de clase estático, la adición ocurrirá esencialmente al comienzo del programa. Esto solo funcionará si la clase tiene un constructor público predeterminado, por lo que la función add aún podría usarse si es necesario algo diferente.

1
aschepler 22 feb. 2018 a las 12:14

Aquí hay una versión con una función add y un map para almacenar los diferentes tipos.

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>();
0
super 22 feb. 2018 a las 12:07

Una solución C ++ 14.

Encabezados requeridos:

#include <memory>
#include <iostream>

Una clase para lanzar cuando no se puede encontrar el índice

class ClassNotIndexed{};

Una clase auxiliar para ayudar a crear un enlace entre el índice y el tipo solicitado:

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

La plantilla principal para nuestra clase de fábrica

template <typename... >
class ClassSelector;

Caso base para clase de fábrica. Cuando se encuentra el índice, cree el unique_pointer, de lo contrario, arroje una excepción:

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();
    }
};

Plantilla variable que toma muchas clases indexadas. Si el índice de la primera clase es una coincidencia, entonces make_unique_pointer verifica la siguiente clase indexada:

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 );
    }
};

Ejemplo:

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;
}
0
Robert Andrzejuk 22 feb. 2018 a las 15:05

Solo puede tener una función de plantilla:

#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;
}

Sin embargo, esto solo permite valores de identificación de clase conocidos en tiempo de compilación.

1
jdehesa 22 feb. 2018 a las 12:09