Gibt es eine Möglichkeit, Objekte aus einer Zeichenfolge zu instanziieren, die ihren Klassennamen enthält?

143

Ich habe eine Datei: Base.h

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

/*etc...*/

und eine andere Datei: 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.*/

Gibt es eine Möglichkeit, diese Zeichenfolge irgendwie in einen tatsächlichen Typ (Klasse) zu konvertieren, sodass BaseFactory nicht alle möglichen abgeleiteten Klassen kennen muss und if () für jede von ihnen hat? Kann ich aus dieser Zeichenfolge eine Klasse erstellen?

Ich denke, dies kann in C # durch Reflection erfolgen. Gibt es etwas ähnliches in C ++?

Gal Goldman
quelle
seine teilweise möglich , mit C ++ 0x und variadische Vorlagen ..
smerlin

Antworten:

227

Nein, es gibt keine, es sei denn, Sie führen das Mapping selbst durch. C ++ hat keinen Mechanismus zum Erstellen von Objekten, deren Typen zur Laufzeit festgelegt werden. Sie können diese Karte jedoch mit einer Karte selbst erstellen:

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

Und dann kannst du es tun

return map[some_string]();

Eine neue Instanz erhalten. Eine andere Idee ist, die Typen selbst registrieren zu lassen:

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

Sie können ein Makro für die Registrierung erstellen

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

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

Ich bin mir sicher, dass es für diese beiden bessere Namen gibt. Eine andere Sache, die hier wahrscheinlich Sinn macht, istshared_ptr .

Wenn Sie eine Reihe nicht verwandter Typen haben, die keine gemeinsame Basisklasse haben, können Sie dem Funktionszeiger boost::variant<A, B, C, D, ...>stattdessen einen Rückgabetyp geben . Wenn Sie eine Klasse Foo, Bar und Baz haben, sieht es so aus:

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;

A boost::variantist wie eine Gewerkschaft. Es erkennt, welcher Typ darin gespeichert ist, indem es nachschaut, welches Objekt zum Initialisieren oder Zuweisen verwendet wurde. Schauen Sie sich hier die Dokumentation an . Schließlich ist die Verwendung eines Rohfunktionszeigers auch etwas altmodisch. Moderner C ++ - Code sollte von bestimmten Funktionen / Typen entkoppelt werden. Vielleicht möchten Sie nach Boost.Functioneinem besseren Weg suchen. Dann würde es so aussehen (die Karte):

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

std::functionwird auch in der nächsten Version von C ++ verfügbar sein, einschließlich std::shared_ptr.

Johannes Schaub - litb
quelle
3
Liebte die Idee, dass sich die abgeleiteten Klassen selbst registrieren. Es ist genau das, wonach ich gesucht habe, um das fest codierte Wissen, von dem abgeleitete Klassen existieren, aus der Fabrik zu entfernen.
Gal Goldman
1
Ursprünglich von somedave in einer anderen Frage gepostet, schlägt dieser Code auf VS2010 mit mehrdeutigen Vorlagenfehlern aufgrund von make_pair fehl. Um dies zu beheben, ändern Sie make_pair in std :: pair <std :: string, Base * ( ) ()> und es sollte diese Fehler beheben. Ich habe auch einige Verknüpfungsfehler erhalten, die durch Hinzufügen von BaseFactory :: map_type behoben wurden. BaseFactory :: map = new map_type (); zu base.cpp
Spencer Rose
9
Wie stellen Sie sicher, dass DerivedB::regdas tatsächlich initialisiert wird? Mein Verständnis ist, dass es möglicherweise überhaupt nicht konstruiert wird, wenn keine Funktion oder kein Objekt in der Übersetzungseinheit derivedb.cppgemäß 3.6.2 definiert ist.
Musiphil
2
Ich liebe die Selbstregistrierung. Zum Kompilieren brauchte ich allerdings eine BaseFactory::map_type * BaseFactory::map = NULL;in meiner CPP-Datei. Ohne dies beschwerte sich der Linker über unbekannte Symbolkarten.
Sven
1
Das funktioniert leider nicht. Wie bereits erwähnt, DerivedB::regwird musiphil nicht initialisiert, wenn keine seiner Funktionen oder Instanzen in der Übersetzungseinheit definiert ist derivedb.cpp. Das bedeutet, dass die Klasse erst registriert wird, wenn sie tatsächlich instanziiert wird. Kennt jemand eine Problemumgehung dafür?
Tomasito665
7

Nein, gibt es nicht. Meine bevorzugte Lösung für dieses Problem besteht darin, ein Wörterbuch zu erstellen, das den Namen der Erstellungsmethode zuordnet. Klassen, die so erstellt werden möchten, registrieren dann eine Erstellungsmethode im Wörterbuch. Dies wird im GoF-Musterbuch ausführlich erläutert .


quelle
5
Möchte jemand herausfinden, um welches Muster es sich handelt, anstatt nur auf das Buch zu zeigen?
Josaphatv
Ich denke, er bezieht sich auf das Registrierungsmuster.
Jiggunjer
2
Für diejenigen, die diese Antwort jetzt lesen, bezieht sich die Antwort meines Erachtens auf die Verwendung des Factory-Musters, einer Implementierung, die ein Wörterbuch verwendet, um zu bestimmen, welche Klasse instanziiert werden soll.
Grimeh
4

Ich habe in einer anderen SO-Frage zu C ++ - Fabriken geantwortet. Bitte sehen Sie dort ob eine flexible Fabrik von Interesse ist. Ich versuche, einen alten Weg von ET ++ zu beschreiben, um Makros zu verwenden, der für mich großartig funktioniert hat.

ET ++ war ein Projekt, um alte MacApp auf C ++ und X11 zu portieren. In der Anstrengung begann Eric Gamma usw. über Design Patterns nachzudenken

Epatel
quelle
2

boost :: function hat eine Factory-Vorlage, die sehr flexibel ist: http://www.boost.org/doc/libs/1_54_0/libs/functional/factory/doc/html/index.html

Ich bevorzuge es jedoch, Wrapper-Klassen zu generieren, die den Mechanismus für die Zuordnung und Objekterstellung verbergen. Das häufigste Szenario ist die Notwendigkeit, verschiedene abgeleitete Klassen einer Basisklasse Schlüsseln zuzuordnen, wobei für alle abgeleiteten Klassen eine gemeinsame Konstruktorsignatur verfügbar ist. Hier ist die Lösung, die ich bisher gefunden habe.

#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

Ich bin im Allgemeinen gegen eine starke Verwendung von Makros, aber ich habe hier eine Ausnahme gemacht. Der obige Code generiert GENERIC_FACTORY_MAX_ARITY + 1-Versionen einer Klasse mit dem Namen GenericFactory_N für jedes N zwischen 0 und GENERIC_FACTORY_MAX_ARITY einschließlich.

Die Verwendung der generierten Klassenvorlagen ist einfach. Angenommen, Sie möchten, dass eine Factory von BaseClass abgeleitete Objekte mithilfe einer Zeichenfolgenzuordnung erstellt. Jedes der abgeleiteten Objekte verwendet 3 Ganzzahlen als Konstruktorparameter.

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

Der Destruktor der GenericFactory_N-Klasse ist virtuell, um Folgendes zu ermöglichen.

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;

Beachten Sie, dass diese Zeile des generischen Factory-Generator-Makros

#define BOOST_PP_FILENAME_1 "GenericFactory.hpp"

Angenommen, die generische Factory-Header-Datei heißt GenericFactory.hpp

texta83
quelle
2

Detaillösung zum Registrieren der Objekte und zum Zugreifen auf sie mit Zeichenfolgennamen.

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

Kompilieren und ausführen (Habe dies mit Eclipse gemacht)

Ausgabe:

Base constructor
test1 constructor m_a=10m_b=20
Base constructor
test1 constructor m_a=30m_b=40
user3458845
quelle
1

Tor Brede Vekterli bietet eine Boost-Erweiterung, die genau die Funktionalität bietet, die Sie suchen. Derzeit ist es mit den aktuellen Boost-Bibliotheken etwas umständlich, aber ich konnte es mit 1.48_0 zum Laufen bringen, nachdem ich seinen Basis-Namespace geändert hatte.

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

Als Antwort auf diejenigen, die sich fragen, warum so etwas (als Reflexion) für c ++ nützlich ist - ich verwende es für Interaktionen zwischen der Benutzeroberfläche und einer Engine -, wählt der Benutzer eine Option in der Benutzeroberfläche aus, und die Engine verwendet die UI-Auswahlzeichenfolge. und erzeugt ein Objekt des gewünschten Typs.

Der Hauptvorteil der Verwendung des Frameworks hier (gegenüber der Verwaltung einer Obstliste irgendwo) besteht darin, dass die Registrierungsfunktion in der Definition jeder Klasse enthalten ist (und nur eine Codezeile benötigt, die die Registrierungsfunktion pro registrierter Klasse aufruft) - im Gegensatz zu einer Datei, die enthält die Fruchtliste, die bei jeder Ableitung einer neuen Klasse manuell hinzugefügt werden muss.

Ich habe die Fabrik zu einem statischen Mitglied meiner Basisklasse gemacht.

DAmann
quelle
0

Dies ist das Werksmuster. Siehe Wikipedia (und dieses Beispiel). Sie können keinen Typ per se aus einer Zeichenfolge ohne einen ungeheuren Hack erstellen. Warum brauchst du das?

dirkgently
quelle
Ich brauche das, weil ich die Zeichenfolgen aus einer Datei lese, und wenn ich das habe, kann ich die Factory so allgemein haben, dass sie nichts wissen muss, um die richtige Instanz zu erstellen. Das ist sehr mächtig.
Gal Goldman
Wollen Sie damit sagen, dass Sie für einen Bus und ein Auto keine unterschiedlichen Klassendefinitionen benötigen, da beide Fahrzeuge sind? Wenn Sie dies jedoch tun, sollte das Hinzufügen einer weiteren Zeile eigentlich kein Problem sein :) Der Kartenansatz hat das gleiche Problem - Sie aktualisieren den Karteninhalt. Das Makro-Ding funktioniert für triviale Klassen.
dirkgently
Ich sage, um in meinem Fall einen Bus oder ein Auto zu erstellen, brauche ich keine anderen Definitionen, sonst würde das Factory-Entwurfsmuster niemals verwendet. Mein Ziel war es, die Fabrik so dumm wie möglich zu machen. Aber ich sehe hier, dass es kein Entrinnen gibt :-)
Gal Goldman