有没有办法从持有类名的字符串中实例化对象?

Gal*_*man 135 c++ inheritance factory instantiation

我有一个文件:Base.h

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

/*etc...*/
Run Code Online (Sandbox Code Playgroud)

和另一个文件: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.*/
Run Code Online (Sandbox Code Playgroud)

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

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

Joh*_*itb 219

不,没有,除非你自己做映射.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>;
Run Code Online (Sandbox Code Playgroud)

然后你就可以做到

return map[some_string]();
Run Code Online (Sandbox Code Playgroud)

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

// 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");
Run Code Online (Sandbox Code Playgroud)

您可以决定为注册创建宏

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

#define REGISTER_DEF_TYPE(NAME) \
    DerivedRegister<NAME> NAME::reg(#NAME)
Run Code Online (Sandbox Code Playgroud)

我相信这两个人的名字更好.在这里使用可能有意义的另一件事是shared_ptr.

如果您有一组没有公共基类的不相关类型,则可以为函数指针指定返回类型boost::variant<A, B, C, D, ...>.就像你有一个类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;
Run Code Online (Sandbox Code Playgroud)

A boost::variant就像一个工会.它通过查看用于初始化或分配对象的对象来了解存储在其中的类型.在这里查看其文档.最后,使用原始函数指针也有点旧.现代C++代码应该与特定的函数/类型分离.您可能希望寻找Boost.Function更好的方法.它看起来像这样(地图):

typedef std::map<std::string, boost::function<variant_type()> > map_type;
Run Code Online (Sandbox Code Playgroud)

std::function将在下一版本的C++中提供,包括std::shared_ptr.

  • 你如何确保实际初始化`DerivedB :: reg`?我的理解是,如果没有在翻译单元`derivedb.cpp`中定义的函数或对象,根据3.6.2,它可能根本不构造. (9认同)
  • 喜欢派生类会注册自己的想法.这正是我所寻找的,一种从工厂中删除存在的派生类的硬编码知识的方法. (3认同)
  • 热爱自我注册。为了进行编译,我需要在cpp文件中使用`BaseFactory :: map_type * BaseFactory :: map = NULL;`。没有这个,链接器就会抱怨未知的符号映射。 (2认同)

小智 7

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

  • 任何人都在关注这个模式,而不仅仅是指出这本书? (4认同)
  • 对于那些现在阅读这个答案的人,我认为答案是指使用Factory模式,这是一种使用字典来确定要实例化的类的实现. (2认同)

Mic*_*fik 6

简短的回答是你不能.请参阅以下SO问题,了解原因:

  1. 为什么C++没有反射?
  2. 如何将反射添加到C++应用程序?