150 votes

Est-il possible d'instancier des objets à partir d'une chaîne contenant leur nom de classe?

J'ai un fichier: Base.h

 class Base;
class DerivedA : public Base;
class DerivedB : public Base;

/*etc...*/
 

et un autre fichier: BaseFactory.h

 #include "Base.h"

class BaseFactory
{
public:
  BaseFactory(const string &sClassName){msClassName = sClassName;};

  Base * Create()
  {
    if(msClassName == "DerivedA")
    {
      return new DerivedA();
    }
    else if(msClassName == "DerivedB")
    {
      return new DerivedB();
    }
    else if(/*etc...*/)
    {
      /*etc...*/
    }
  };
private:
  string msClassName;
};

/*etc.*/
 

Existe-t-il un moyen de convertir en quelque sorte cette chaîne en un type réel (classe), afin que BaseFactory n'ait pas à connaître toutes les classes dérivées possibles et à avoir if () pour chacune d'entre elles? Puis-je produire une classe à partir de cette chaîne?

Je pense que cela peut être fait en C # à travers la réflexion. Y a-t-il quelque chose de similaire en C ++?

239voto

Johannes Schaub - litb Points 256113

Non, il n'en est aucun, à moins que vous ne le mappage de vous-même. C++ n'a pas de mécanisme pour créer des objets dont le type est déterminé au moment de l'exécution. Vous pouvez utiliser une carte pour faire de la cartographie vous-même, cependant:

template<typename T> Base * createInstance() { return new T; }

typedef std::map<std::string, Base*(*)()> map_type;

map_type map;
map["DerivedA"] = &createInstance<DerivedA>;
map["DerivedB"] = &createInstance<DerivedB>;

Et puis vous pouvez le faire

return map[some_string]();

L'obtention d'une nouvelle instance. Une autre idée est d'avoir les types s'inscrire lui-même:

// in base.hpp:
template<typename T> Base * createT() { return new T; }

struct BaseFactory {
    typedef std::map<std::string, Base*(*)()> map_type;

    static Base * createInstance(std::string const& s) {
        map_type::iterator it = getMap()->find(s);
        if(it == getMap()->end())
            return 0;
        return it->second();
    }

protected:
    static map_type * getMap() {
        // never delete'ed. (exist until program termination)
        // because we can't guarantee correct destruction order 
        if(!map) { map = new map_type; } 
        return map; 
    }

private:
    static map_type * map;
};

template<typename T>
struct DerivedRegister : BaseFactory { 
    DerivedRegister(std::string const& s) { 
        getMap()->insert(std::make_pair(s, &createT<T>));
    }
};

// in derivedb.hpp
class DerivedB {
    ...;
private:
    static DerivedRegister<DerivedB> reg;
};

// in derivedb.cpp:
DerivedRegister<DerivedB> DerivedB::reg("DerivedB");

Vous pouvez décider de créer une macro pour l'enregistrement

#define REGISTER_DEC_TYPE(NAME) \
    static DerivedRegister<NAME> reg

#define REGISTER_DEF_TYPE(NAME) \
    DerivedRegister<NAME> NAME::reg(#NAME)

Je suis sûr qu'il ya des meilleurs noms pour ces deux. Une autre chose qui fait sens pour l'utiliser ici est - shared_ptr.

Si vous avez un ensemble de sans rapport avec les types qui n'ont aucune commune de la classe de base, vous pouvez donner le pointeur de fonction d'un type de retour d' boost::variant<A, B, C, D, ...> à la place. Comme si vous avez une classe Foo, Bar et Baz, il ressemble à ceci:

typedef boost::variant<Foo, Bar, Baz> variant_type;
template<typename T> variant_type createInstance() { 
    return variant_type(T()); 
}

typedef std::map<std::string, variant_type (*)()> map_type;

Un boost::variant , c'est comme une union. Il sait de quel type est stockée en regardant ce que l'objet a été utilisé pour l'initialisation ou de l'affectation. Jetez un oeil à sa documentation ici. Enfin, l'utilisation d'un raw de pointeur de fonction est aussi un peu un peu vieux. Moderne de code C++ doit être découplée de fonctions spécifiques / types d'. Vous pouvez regarder en Boost.Function chercher une meilleure façon. Il devrait ressembler à ceci ensuite (à la carte):

typedef std::map<std::string, boost::function<variant_type()> > map_type;

std::function sera disponible dans la prochaine version de C++, dont l' std::shared_ptr.

5voto

Michael Kristofik Points 16035

La réponse courte est que vous ne pouvez pas. Voir ces questions SO pour pourquoi:

  1. Pourquoi C ++ n'a-t-il pas de réflexion?
  2. Comment puis-je ajouter de la réflexion à une application C ++?

4voto

epatel Points 32451

J'ai répondu dans un autre DONC, la question sur le C++ usines. Veuillez voir si un flexible de l'usine est de l'intérêt. J'essaie de décrire une ancienne façon de ET++ pour utiliser les macros qui a beaucoup travaillé pour moi.

ET++ a été un projet de port vieux MacApp C++ et X11. Dans l'effort de Eric Gamma etc commencé à réfléchir à des Modèles de Conception

2voto

texta83 Points 21

boost::fonctionnel dispose d'une usine modèle qui est assez souple: http://www.boost.org/doc/libs/1_54_0/libs/functional/factory/doc/html/index.html

Ma préférence est bien pour générer des classes wrapper qui cachent la cartographie et de la création de l'objet mécanisme. La commune de scénario que je rencontre est la nécessité de cartographier les différentes classes dérivées de certains de la classe de base de touches, où les classes dérivées tous ont en commun un constructeur de signature. Voici la solution que j'ai trouvé jusqu'à présent.

#ifndef GENERIC_FACTORY_HPP_INCLUDED

//BOOST_PP_IS_ITERATING is defined when we are iterating over this header file.
#ifndef BOOST_PP_IS_ITERATING

    //Included headers.
    #include <unordered_map>
    #include <functional>
    #include <boost/preprocessor/iteration/iterate.hpp>
    #include <boost/preprocessor/repetition.hpp>

    //The GENERIC_FACTORY_MAX_ARITY directive controls the number of factory classes which will be generated.
    #ifndef GENERIC_FACTORY_MAX_ARITY
        #define GENERIC_FACTORY_MAX_ARITY 10
    #endif

    //This macro magic generates GENERIC_FACTORY_MAX_ARITY + 1 versions of the GenericFactory class.
    //Each class generated will have a suffix of the number of parameters taken by the derived type constructors.
    #define BOOST_PP_FILENAME_1 "GenericFactory.hpp"
    #define BOOST_PP_ITERATION_LIMITS (0,GENERIC_FACTORY_MAX_ARITY)
    #include BOOST_PP_ITERATE()

    #define GENERIC_FACTORY_HPP_INCLUDED

#else

    #define N BOOST_PP_ITERATION() //This is the Nth iteration of the header file.
    #define GENERIC_FACTORY_APPEND_PLACEHOLDER(z, current, last) BOOST_PP_COMMA() BOOST_PP_CAT(std::placeholders::_, BOOST_PP_ADD(current, 1))

    //This is the class which we are generating multiple times
    template <class KeyType, class BasePointerType BOOST_PP_ENUM_TRAILING_PARAMS(N, typename T)>
    class BOOST_PP_CAT(GenericFactory_, N)
    {
        public:
            typedef BasePointerType result_type;

        public:
            virtual ~BOOST_PP_CAT(GenericFactory_, N)() {}

            //Registers a derived type against a particular key.
            template <class DerivedType>
            void Register(const KeyType& key)
            {
                m_creatorMap[key] = std::bind(&BOOST_PP_CAT(GenericFactory_, N)::CreateImpl<DerivedType>, this BOOST_PP_REPEAT(N, GENERIC_FACTORY_APPEND_PLACEHOLDER, N));
            }

            //Deregisters an existing registration.
            bool Deregister(const KeyType& key)
            {
                return (m_creatorMap.erase(key) == 1);
            }

            //Returns true if the key is registered in this factory, false otherwise.
            bool IsCreatable(const KeyType& key) const
            {
                return (m_creatorMap.count(key) != 0);
            }

            //Creates the derived type associated with key. Throws std::out_of_range if key not found.
            BasePointerType Create(const KeyType& key BOOST_PP_ENUM_TRAILING_BINARY_PARAMS(N,const T,& a)) const
            {
                return m_creatorMap.at(key)(BOOST_PP_ENUM_PARAMS(N,a));
            }

        private:
            //This method performs the creation of the derived type object on the heap.
            template <class DerivedType>
            BasePointerType CreateImpl(BOOST_PP_ENUM_BINARY_PARAMS(N,const T,& a))
            {
                BasePointerType pNewObject(new DerivedType(BOOST_PP_ENUM_PARAMS(N,a)));
                return pNewObject;
            }

        private:
            typedef std::function<BasePointerType (BOOST_PP_ENUM_BINARY_PARAMS(N,const T,& BOOST_PP_INTERCEPT))> CreatorFuncType;
            typedef std::unordered_map<KeyType, CreatorFuncType> CreatorMapType;
            CreatorMapType m_creatorMap;
    };

    #undef N
    #undef GENERIC_FACTORY_APPEND_PLACEHOLDER

#endif // defined(BOOST_PP_IS_ITERATING)
#endif // include guard

Je suis généralement contre l'lourds l'utilisation des macros, mais je l'ai fait ici une exception. Le code ci-dessus génère GENERIC_FACTORY_MAX_ARITY + 1 versions d'une classe nommée GenericFactory_N, pour chaque N entre 0 et GENERIC_FACTORY_MAX_ARITY inclusive.

À l'aide de la classe générée modèles est facile. Supposons que vous souhaitiez une usine à créer BaseClass objets dérivés à l'aide d'un mappage de chaîne. Chacun des objets dérivés de prendre 3 entiers comme les paramètres du constructeur.

#include "GenericFactory.hpp"

typedef GenericFactory_3<std::string, std::shared_ptr<BaseClass>, int, int int> factory_type;

factory_type factory;
factory.Register<DerivedClass1>("DerivedType1");
factory.Register<DerivedClass2>("DerivedType2");
factory.Register<DerivedClass3>("DerivedType3");

factory_type::result_type someNewObject1 = factory.Create("DerivedType2", 1, 2, 3);
factory_type::result_type someNewObject2 = factory.Create("DerivedType1", 4, 5, 6);

Le GenericFactory_N destructeur de classe est virtuel pour permettre à la suivante.

class SomeBaseFactory : public GenericFactory_2<int, BaseType*, std::string, bool>
{
    public:
        SomeBaseFactory() : GenericFactory_2()
        {
            Register<SomeDerived1>(1);
            Register<SomeDerived2>(2);
        }
}; 

SomeBaseFactory factory;
SomeBaseFactory::result_type someObject = factory.Create(1, "Hi", true);
delete someObject;

Notez que cette ligne du générique de l'usine de générateur de macro

#define BOOST_PP_FILENAME_1 "GenericFactory.hpp"

Suppose le générique de l'usine en-tête de fichier est nommé GenericFactory.php

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X