【问题标题】:Is there a way to instantiate objects from a string holding their class name?有没有办法从保存类名的字符串中实例化对象?
【发布时间】:2010-10-09 14:24:56
【问题描述】:

我有一个文件:Base.h

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

/*etc...*/

还有另一个文件: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.*/

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

我认为这可以通过反射在 C# 中完成。 C++中有类似的东西吗?

【问题讨论】:

  • 部分可能使用 C++0x 和可变参数模板..

标签: c++ inheritance factory instantiation


【解决方案1】:

不,没有,除非您自己进行映射。 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&lt;A, B, C, D, ...&gt;。就像你有一个类 Foo、Bar 和 Baz,它看起来像这样:

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;

boost::variant 就像一个联合体。它通过查看用于初始化或分配给它的对象来知道其中存储了哪种类型。查看它的文档here。最后,使用原始函数指针也有点陈旧。现代 C++ 代码应该与特定的函数/类型分离。您可能想查看Boost.Function 以寻找更好的方法。它看起来像这样(地图):

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

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

【讨论】:

  • 喜欢派生类将自己注册的想法。这正是我一直在寻找的,一种从工厂中删除关于存在哪些派生类的硬编码知识的方法。
  • 最初由 somedave 在另一个问题中发布,此代码在 VS2010 上失败,由于 make_pair 导致模板错误模棱两可。要修复,请将 make_pair 更改为 std::pair<:string>)()> 并且它应该修复这些错误。我还遇到了一些链接错误,通过添加 BaseFactory::map_type BaseFactory::map = new map_type();到 base.cpp
  • 如何确保DerivedB::reg 实际被初始化?我的理解是,如果翻译单元derivedb.cpp中没有定义函数或对象,则可能根本无法构造,按照3.6.2。
  • 喜欢自助注册。虽然我需要在我的 cpp 文件中使用 BaseFactory::map_type * BaseFactory::map = NULL; 进行编译。没有这个,链接器会抱怨未知的符号映射。
  • 不幸的是,这不起作用。正如musiphil 已经指出的那样,如果DerivedB::reg 在翻译单元derivedb.cpp 中没有定义任何函数或实例,则不会初始化DerivedB::reg。这意味着该类在实际实例化之前不会被注册。有人知道解决方法吗?
【解决方案2】:

不,没有。我对这个问题的首选解决方案是创建一个将名称映射到创建方法的字典。想要像这样创建的类然后向字典注册创建方法。这在GoF patterns book 中有详细讨论。

【讨论】:

  • 有人关心这是什么模式,而不仅仅是指着书吗?
  • 我认为他指的是注册表模式。
  • 对于那些现在阅读这个答案的人,我相信答案是指使用工厂模式,一种使用字典来确定要实例化哪个类的实现。
【解决方案3】:

简短的回答是你不能。请参阅这些 SO 问题了解原因:

  1. Why does C++ not have reflection?
  2. How can I add reflection to a C++ application?

【讨论】:

    【解决方案4】:

    我已经回答了关于 C++ 工厂的另一个 SO 问题。如果对灵活工厂感兴趣,请参阅there。我尝试从 ET++ 中描述一种使用宏的旧方法,这对我来说非常有用。

    ET++ 是一个将旧 MacApp 移植到 C++ 和 X11 的项目。在此过程中,Eric Gamma 等人开始考虑设计模式

    【讨论】:

      【解决方案5】:

      boost::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
      

      我通常反对大量使用宏,但我在这里做了一个例外。上面的代码生成一个名为 GenericFactory_N 的类的 GENERIC_FACTORY_MAX_ARITY + 1 个版本,每个 N 介于 0 和 GENERIC_FACTORY_MAX_ARITY 之间。

      使用生成的类模板很容易。假设您希望工厂使用字符串映射创建 BaseClass 派生对象。每个派生对象都采用 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;
      

      注意这行通用工厂生成器宏

      #define BOOST_PP_FILENAME_1 "GenericFactory.hpp"
      

      假设通用工厂头文件名为 GenericFactory.hpp

      【讨论】:

        【解决方案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】:

          Tor Brede Vekterli 提供了一个增强扩展,可提供您所寻求的功能。目前,它与当前的 boost 库配合有点尴尬,但我能够在更改其基本命名空间后使其与 1.48_0 一起使用。

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

          回答那些质疑为什么这样的东西(如反射)对 c++ 有用的人的回答 - 我将它用于 UI 和引擎之间的交互 ​​- 用户在 UI 中选择一个选项,然后引擎获取 UI选择字符串,并生成所需类型的对象。

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

          我将工厂设置为我的基类的静态成员。

          【讨论】:

            【解决方案8】:

            意思是Java中的反射。 这里有一些信息: http://msdn.microsoft.com/en-us/library/y0114hz2(VS.80).aspx

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

            【讨论】:

            • 你所指的页面上的东西与标准c++相差甚远
            【解决方案9】:

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

            【讨论】:

            • 我需要这个,因为我从一个文件中读取字符串,如果我有这个,那么我可以拥有如此通用的工厂,它不需要知道任何东西就可以创建正确的实例。这是非常强大的。
            • 那么,您是说您不需要为 Bus 和 Car 定义不同的类,因为它们都是 Vehicles?但是,如果您这样做,添加另一行应该不是问题:) 地图方法有同样的问题——您更新地图内容。宏适用于琐碎的类。
            • 我的意思是,在我的情况下,为了创建公共汽车或汽车,我不需要不同的定义,否则工厂设计模式将永远不会被使用。我的目标是让工厂尽可能地愚蠢。但我在这里看到没有逃脱 :-)
            【解决方案10】:

            是的,可以不使用框架和宏,只获取类方法和构造函数的内存地址。当为此操作配置时,您可以从链接器生成的映射中检索它们。

            访问本站

            https://ealaframework.no-ip.org/wiki/page/c.reference

            【讨论】:

            • 正如目前所写,您的答案尚不清楚。请编辑以添加其他详细信息,以帮助其他人了解这如何解决所提出的问题。你可以在help center找到更多关于如何写好答案的信息。
            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2022-12-10
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2010-11-23
            相关资源
            最近更新 更多