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

时间:2009-02-24 16:00:00

标签: c++ inheritance factory instantiation

我有一个文件: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()?我可以用这个字符串生成一个类吗?

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

9 个答案:

答案 0 :(得分:211)

不,没有,除非你自己做映射。 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;

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

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

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

答案 1 :(得分:7)

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

中有详细讨论

答案 2 :(得分:6)

简短的回答是你不能。请参阅以下SO问题:

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

答案 3 :(得分:4)

我在另一个关于C ++工厂的问题中回答过。如果您感兴趣的是灵活的工厂,请参阅there。我尝试用ET ++描述一种旧方法来使用对我来说很有用的宏。

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

答案 4 :(得分: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

答案 5 :(得分:1)

像Java一样反映意义。 这里有一些信息: http://msdn.microsoft.com/en-us/library/y0114hz2(VS.80).aspx

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

答案 6 :(得分:1)

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

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

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

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

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

答案 7 :(得分:1)

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

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

答案 8 :(得分:0)

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