web-dev-qa-db-fra.com

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), de sorte que BaseFactory ne doive pas connaître toutes les classes dérivées possibles et disposer de 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 # par le biais de la réflexion. Y a-t-il quelque chose de similaire en C++?

134
Gal Goldman

Non, il n'y en a pas, à moins que vous ne cartographiez vous-même. C++ ne dispose d'aucun mécanisme pour créer des objets dont les types sont déterminés lors de l'exécution. Vous pouvez utiliser une carte pour le faire 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 alors tu peux faire

return map[some_string]();

Obtenir une nouvelle instance. Une autre idée est que les types s’enregistrent:

// 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 existe de meilleurs noms pour ces deux-là. Une autre chose qui a probablement du sens à utiliser ici est shared_ptr.

Si vous avez un ensemble de types non liés qui n'ont pas de classe de base commune, vous pouvez donner au pointeur de fonction un type de retour de boost::variant<A, B, C, D, ...> au lieu. Comme si vous avez une classe Foo, Bar et Baz, cela 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;

UNE boost::variant est comme une union. Il sait quel type y est stocké en cherchant quel objet a été utilisé pour l'initialiser ou l'affecter. Regardez sa documentation ici . Enfin, l'utilisation d'un pointeur de fonction brut est également un peu ancienne. Le code C++ moderne doit être découplé de fonctions/types spécifiques. Vous voudrez peut-être examiner Boost.Function pour trouver une meilleure solution. Cela ressemblerait à ceci alors (la carte):

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

std::function sera également disponible dans la prochaine version de C++, y compris std::shared_ptr.

215

Non il n'y en a pas. Ma solution préférée à ce problème est de créer un dictionnaire qui mappe nom à méthode de création. Les classes qui souhaitent être créées de la sorte enregistrent ensuite une méthode de création avec le dictionnaire. Ceci est discuté en détail dans le livre de patterns GoF .

7
anon

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

  1. Pourquoi C++ n'a pas de réflexion?
  2. Comment puis-je ajouter une réflexion à une application C++?
6
Michael Kristofik

J'ai répondu à une autre SO question sur les usines C++. Veuillez voir ici si une usine flexible est intéressante. J'essaie de décrire une ancienne méthode utilisée par ET ++ pour utiliser des macros ce qui a très bien fonctionné pour moi.

ET ++ était un projet visant à porter l'ancien MacApp en C++ et X11. Dans le cadre de cet effort, Eric Gamma etc. a commencé à réfléchir à Design Patterns

4
epatel

boost :: functional a un template d'usine assez flexible: http://www.boost.org/doc/libs/1_54_0/libs/functional/factory/doc/html/index.html

Ma préférence est toutefois de générer des classes wrapper masquant le mappage et le mécanisme de création d’objet. Le scénario courant que je rencontre est la nécessité de mapper différentes classes dérivées d'une classe de base sur des clés, les classes dérivées disposant toutes d'une signature de constructeur commune. Voici la solution que j'ai trouvée 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 opposé à une utilisation intensive des macros, mais j’ai fait une exception ici. Le code ci-dessus génère les versions GENERIC_FACTORY_MAX_ARITY + 1 d'une classe nommée GenericFactory_N, pour chaque N compris entre 0 et GENERIC_FACTORY_MAX_ARITY inclus.

L'utilisation des modèles de classe générés est simple. Supposons que vous souhaitiez qu'une usine crée des objets dérivés BaseClass à l'aide d'un mappage de chaîne. Chacun des objets dérivés prend 3 entiers en tant que paramètres de 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 destructeur de classe GenericFactory_N est virtuel pour permettre les opérations suivantes.

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 de la macro du générateur d’usine générique

#define BOOST_PP_FILENAME_1 "GenericFactory.hpp"

Suppose que le fichier d'en-tête de la fabrique générique s'appelle GenericFactory.hpp.

2
texta83

Solution détaillée pour enregistrer les objets et y accéder avec des noms de chaîne.

common.h:

#ifndef COMMON_H_
#define COMMON_H_


#include<iostream>
#include<string>
#include<iomanip>
#include<map>

using namespace std;
class Base{
public:
    Base(){cout <<"Base constructor\n";}
    virtual ~Base(){cout <<"Base destructor\n";}
};
#endif /* COMMON_H_ */

test1.h:

/*
 * test1.h
 *
 *  Created on: 28-Dec-2015
 *      Author: ravi.prasad
 */

#ifndef TEST1_H_
#define TEST1_H_
#include "common.h"

class test1: public Base{
    int m_a;
    int m_b;
public:
    test1(int a=0, int b=0):m_a(a),m_b(b)
    {
        cout <<"test1 constructor m_a="<<m_a<<"m_b="<<m_b<<endl;
    }
    virtual ~test1(){cout <<"test1 destructor\n";}
};



#endif /* TEST1_H_ */

3. test2.h
#ifndef TEST2_H_
#define TEST2_H_
#include "common.h"

class test2: public Base{
    int m_a;
    int m_b;
public:
    test2(int a=0, int b=0):m_a(a),m_b(b)
    {
        cout <<"test1 constructor m_a="<<m_a<<"m_b="<<m_b<<endl;
    }
    virtual ~test2(){cout <<"test2 destructor\n";}
};


#endif /* TEST2_H_ */

main.cpp:

#include "test1.h"
#include "test2.h"

template<typename T> Base * createInstance(int a, int b) { return new T(a,b); }

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

map_type mymap;

int main()
{

    mymap["test1"] = &createInstance<test1>;
    mymap["test2"] = &createInstance<test2>;

     /*for (map_type::iterator it=mymap.begin(); it!=mymap.end(); ++it)
        std::cout << it->first << " => " << it->second(10,20) << '\n';*/

    Base *b = mymap["test1"](10,20);
    Base *b2 = mymap["test2"](30,40);

    return 0;
}

Compilez-le et lancez-le (avec Eclipse)

Sortie:

Base constructor
test1 constructor m_a=10m_b=20
Base constructor
test1 constructor m_a=30m_b=40
2
user3458845

Sens de réflexion comme en Java. il y a quelques informations ici: http://msdn.Microsoft.com/en-us/library/y0114hz2 (VS.80) .aspx

En règle générale, recherchez sur Google "réflexion c ++"

1
Ido Weinstein

Tor Brede Vekterli fournit une extension boost qui donne exactement les fonctionnalités que vous recherchez. Actuellement, il s’agit d’un ajustement quelque peu délicat avec les librairies boost actuelles, mais j’ai réussi à le faire fonctionner avec 1.48_0 après avoir changé son espace de noms de base.

http://arcticinteractive.com/static/boost/libs/factory/doc/html/factory/factory.html#factory.factory.reference

En réponse à ceux qui s'interrogent sur les raisons pour lesquelles une telle chose (comme réflexion) serait utile pour c ++ - je l'utilise pour les interactions entre l'interface utilisateur et un moteur - l'utilisateur sélectionne une option dans l'interface utilisateur et le moteur prend la chaîne de sélection de l'interface utilisateur, et produit un objet du type souhaité.

Le principal avantage de l'utilisation du cadre ici (par rapport au maintien d'une liste de fruits quelque part) est que la fonction d'enregistrement est définie dans la définition de chaque classe (et ne nécessite qu'une seule ligne de code appelant la fonction d'enregistrement par classe enregistrée) - par opposition à un fichier contenant la liste de fruits, qui doit être ajoutée manuellement à chaque fois qu'une nouvelle classe est dérivée.

J'ai fait de l'usine un membre statique de ma classe de base.

1
DAmann

Ceci est le modèle d'usine. Voir wikipedia (et this exemple). Vous ne pouvez pas créer un type en soi à partir d'une chaîne sans un piratage flagrant. Pourquoi avez-vous besoin de cela?

0
dirkgently