代码之家  ›  专栏  ›  技术社区  ›  Gal Goldman

是否有一种方法可以从保存其类名的字符串实例化对象?

  •  125
  • Gal Goldman  · 技术社区  · 15 年前

    我有一个文件:Base.h

    class Base;
    class DerivedA : public Base;
    class DerivedB : public Base;
    
    /*etc...*/
    

    #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.*/
    

    有没有办法将这个字符串转换成实际的类型(类),这样BaseFactory就不必知道所有可能的派生类,并且每个派生类都有if()?我可以从这个字符串生成一个类吗?

    我认为这可以通过C#反射来实现。C++中有类似的东西吗?

    9 回复  |  直到 13 年前
        1
  •  239
  •   That Realty Programmer Guy    10 年前

    没有,没有,除非你自己做映射。C++没有机制来创建在运行时确定类型的对象。不过,您可以使用地图自己进行映射:

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

    然后你就可以做了

    return map[some_string]();
    

    获取一个新实例。另一个想法是让类型自己注册:

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

    您可以决定为注册创建宏

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

    不过我相信这两个人还有更好的名字。在这里使用另一个可能有意义的东西是 shared_ptr

    如果有一组没有公共基类的不相关类型,则可以为函数指针提供 boost::variant<A, B, C, D, ...>

    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::variant 就像一个联盟。它通过查看用于初始化或分配给它的对象来知道存储在其中的类型。看看它的文档 here . 最后,原始函数指针的使用也有点陈旧。现代C++代码应该与特定的函数/类型进行解耦。你可能想调查一下 Boost.Function 寻找更好的方法。它看起来是这样的(地图):

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

    std::function 将在下一版本的C++中也可用,包括 std::shared_ptr .

        2
  •  6
  •   anon anon    15 年前

    不,没有。我对这个问题的首选解决方案是创建一个字典,将名称映射到创建方法。希望像这样创建的类然后向字典注册一个创建方法。本节将对此进行详细讨论 GoF patterns book .

        4
  •  4
  •   Community Neeleshkumar S    7 年前

    我在另一个关于C++工厂的问题上回答了问题。请看 there 如果一个灵活的工厂感兴趣。我试图描述一种从ET++到使用宏的老方法,这种方法对我来说非常有效。

    ET++ 是一个将旧Mac App移植到C++和X11的项目。在它的努力下,Eric Gamma等开始思考 设计模式

        5
  •  2
  •   texta83    11 年前

    functional有一个非常灵活的工厂模板: http://www.boost.org/doc/libs/1_54_0/libs/functional/factory/doc/html/index.html

    不过,我的首选是生成隐藏映射和对象创建机制的包装器类。我遇到的常见场景是需要将某些基类的不同派生类映射到键,其中派生类都有一个可用的公共构造函数签名。这是我到目前为止提出的解决方案。

    #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
    

    我通常反对大量使用宏,但我在这里破例。上面的代码为0和泛型工厂最大数量(含)之间的每个N生成名为GenericFactory\u N的类的泛型工厂最大数量+1个版本。

    使用生成的类模板很容易。假设您希望工厂使用字符串映射创建基类派生对象。每个派生对象都采用3个整数作为构造函数参数。

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

    GenericFactory\N类析构函数是虚拟的,允许执行以下操作。

    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;
    

    请注意,这一行是通用factory generator宏的

    #define BOOST_PP_FILENAME_1 "GenericFactory.hpp"
    

        6
  •  2
  •   Martijn Pieters    6 年前

    注册对象并使用字符串名称访问它们的详细解决方案。

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

    编译并运行它(使用Eclipse完成此操作)

    输出:

    Base constructor
    test1 constructor m_a=10m_b=20
    Base constructor
    test1 constructor m_a=30m_b=40
    
        7
  •  1
  •   DAmann    12 年前

    Tor Brede Vekterli提供了一个增强扩展,它提供了您想要的功能。目前,使用当前的boostlibs有点不方便,但在更改了1.48_0的基本名称空间后,我能够让它使用1.48_0。

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

    对于那些质疑为什么这样的事情(如反射)对C++有用的人来说,我用它来处理UI和引擎之间的交互——用户在UI中选择一个选项,引擎取UI选择字符串,并产生一个所需类型的对象。

    在这里使用框架(而不是在某处维护水果列表)的主要好处是,注册函数位于每个类的定义中(每个注册类只需要一行代码调用注册函数),而不是包含水果列表的文件,每次派生新类时都必须手动将其添加到。

    我使工厂成为基类的静态成员。

        8
  •  0
  •   Ido Weinstein    15 年前

    在Java中表示反射。 http://msdn.microsoft.com/en-us/library/y0114hz2(VS.80).aspx

    一般来说,在谷歌搜索“c++反射”

        9
  •  0
  •   dirkgently    15 年前

    这是工厂模式。参见维基百科(和 this 示例)。如果没有一些惊人的技巧,就无法从字符串创建类型本身。你为什么需要这个?