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


143

我有一个文件: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不必知道所有可能的Derived类,并且每个类都具有if()?我可以用这个字符串产生一个类吗?

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


它与的C ++ 0x和可变参数模板部分可能..
smerlin

Answers:


227

不,没有,除非您自己进行映射。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, ...>。就像您有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;

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

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

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


3
喜欢派生类将自己注册的想法。这正是我在寻找的一种方法,它消除了工厂中存在哪些派生类的硬编码知识。
Gal Goldman,

1
最初由somedave在另一个问题中发布,由于make_pair,此代码在VS2010上由于模板错误模棱两可而失败。要解决此问题,请将make_pair更改为std :: pair <std :: string,Base *()()>,它应该可以修复这些错误。我还得到了一些链接错误,这些错误通过添加BaseFactory :: map_type BaseFactory :: map = new map_type();得以解决。到base.cpp
Spencer Rose

9
您如何确保它DerivedB::reg实际上已初始化?我的理解是,如果翻译单元没有derivedb.cpp按照3.6.2 定义功能或对象,则可能根本无法构建它。
musiphil

2
热爱自我注册。要编译,虽然我需要 BaseFactory::map_type * BaseFactory::map = NULL;在cpp文件中。没有这个,链接器就会抱怨未知的符号映射。

1
不幸的是,这行不通。正如musiphil已经指出的那样,DerivedB::reg如果未在翻译单元中定义其功能或实例,则不会初始化derivedb.cpp。这意味着该类在实际实例化之前不会注册。有人知道解决方法吗?
Tomasito665

7

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


5
有人在乎确定这是哪种模式,而不仅仅是指向书吗?
josaphatv 2014年

我认为他指的是注册表模式。
jiggunjer

2
对于那些现在阅读此答案的人,我相信答案是指使用Factory模式,该模式使用字典来确定要实例化的类。
Grimeh 2015年


4

我在另一个有关C ++工厂的问题中回答了。请查看那里是否有一家灵活的工厂。我尝试描述从ET ++使用宏的一种旧方法,该方法对我非常有用。

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


2

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

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

使用生成的类模板很容易。假设您希望工厂使用字符串映射创建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


2

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

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


1

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选择字符串,并产生所需类型的对象。

在这里使用该框架(而不是在某个地方维护一个水果清单)的主要好处是,注册功能在每个类的定义中(并且只需要一行代码为每个注册的类调用注册功能)-而不是包含水果清单,每次派生新类时必须将其手动添加。

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


0

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


我之所以需要它,是因为我从文件中读取了字符串,如果有的话,那么我可以拥有一个通用的工厂,以至于它不需要知道任何东西就可以创建正确的实例。这是非常强大的。
Gal Goldman

因此,您是说您不需要公共汽车和汽车的不同类定义,因为它们都是车辆?但是,如果这样做,添加另一行并不是真正的问题:)地图方法也有同样的问题-您更新地图内容。宏thing适用于琐碎的类。
2009年

我的意思是,要创建一辆公共汽车或一辆汽车,我不需要其他定义,否则,工厂设计模式将永远不会被使用。我的目标是让工厂尽可能地愚蠢。但我在这里看到无法逃脱:-)
Gal Goldman
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.