클래스 이름을 가진 문자열에서 객체를 인스턴스화하는 방법이 있습니까?


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가 가능한 모든 파생 클래스를 알 필요가 없으며 각 클래스에 대해 if ()를 가질 필요가 없습니까? 이 문자열에서 수업을 만들 수 있습니까?

C #에서 Reflection을 통해이 작업을 수행 할 수 있다고 생각합니다. C ++에 비슷한 것이 있습니까?


C ++ 0x 및 가변 템플릿으로 부분적으로 가능합니다.
smerlin

답변:


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가 원래 게시 한이 코드는 VS2010에서 make_pair로 인해 모호한 템플릿 오류로 실패합니다. 수정하려면 make_pair를 std :: pair <std :: string, Base * ( ) ()>로 변경하면 해당 오류를 수정해야합니다. 또한 BaseFactory :: map_type을 추가하여 수정 된 일부 링크 오류가 있습니다. BaseFactory :: map = new map_type (); to base.cpp
스펜서 로즈 1

9
DerivedB::reg실제로 초기화 되었는지 어떻게 확인 합니까? derivedb.cpp3.6.2 에 따라 변환 단위에 정의 된 기능이나 객체가 없으면 전혀 구성되지 않을 수 있습니다 .
musiphil

2
자기 등록을 좋아하십시오. BaseFactory::map_type * BaseFactory::map = NULL;cpp 파일에 필요하지만 컴파일하려면 . 이것이 없으면, 링커는 미지의 심볼 맵에 대해 불평했다.
Sven

1
불행히도 이것은 작동하지 않습니다. musiphil이 이미 지적했듯이 DerivedB::reg번역 단위에 함수 나 인스턴스가 정의되어 있지 않으면 초기화되지 않습니다 derivedb.cpp. 즉, 실제로 인스턴스화 될 때까지 클래스가 등록되지 않습니다. 아무도 그 해결 방법을 알고 있습니까?
Tomasito665

7

없습니다. 이 문제에 대한 내가 선호하는 해결책은 이름을 생성 방법에 매핑하는 사전을 만드는 것입니다. 이와 같이 생성하려는 클래스는 사전에 생성 방법을 등록합니다. 이에 대해서는 GoF 패턴 책 에서 자세히 설명합니다 .


5
아무도 책을 가리 키기보다는 이것이 어떤 패턴인지 식별하려고 신경 쓰는가?
josaphatv

그가 레지스트리 패턴을 언급하고 있다고 생각합니다.
jiggunjer

2
지금이 대답을 읽는 사람들에게는 대답이 인스턴스화 할 클래스를 결정하기 위해 사전을 사용하는 구현 인 Factory 패턴을 사용하는 것을 말하는 것으로 생각합니다.
Grimeh


4

C ++ 팩토리에 대한 또 다른 SO 질문에 대답했습니다. 유연한 공장에 관심 있다면 여기를 참조하십시오 . 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는 사용자가 원하는 기능을 정확하게 제공하는 확장 기능을 제공합니다. 현재 현재 부스트 라이브러리에 약간 어색하지만 적합하지만 기본 네임 스페이스를 변경 한 후 1.48_0으로 작동시킬 수있었습니다.

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

왜 그런 것들 (반사와 같은)이 c ++에 유용한 지 의문을 가진 사람들에 대한 대답-UI와 엔진 간의 상호 작용에 사용합니다-사용자는 UI에서 옵션을 선택하고 엔진은 UI 선택 문자열을 가져옵니다. 원하는 유형의 객체를 생성합니다.

여기에 프레임 워크를 사용하면 과일 목록을 어딘가에 유지하는 것의 주요 이점은 등록 기능이 각 클래스의 정의에 있으며 파일이있는 파일과 달리 등록 된 클래스마다 등록 기능을 호출하는 한 줄의 코드 만 필요하다는 것입니다 과일 목록-새 클래스가 파생 될 때마다 수동으로 추가해야합니다.

팩토리를 기본 클래스의 정적 멤버로 만들었습니다.


0

이것은 공장 패턴입니다. Wikipedia (및 예제)를 참조하십시오 . 엄청나게 많은 핵이 없으면 문자열 자체에서 유형을 만들 수 없습니다. 왜 이것이 필요한가요?


파일에서 문자열을 읽었으므로 이것이 필요합니다.이 경우 공장을 너무 일반화하여 올바른 인스턴스를 만들기 위해 아무것도 알 필요가 없습니다. 이것은 매우 강력합니다.
Gal Goldman

버스와 자동차가 모두 차량이기 때문에 다른 클래스 정의가 필요하지 않다고 말하는가? 그러나 그렇게하면 다른 줄을 추가해도 실제로 문제가되지 않습니다. :)지도 접근 방식에는 동일한 문제가 있습니다. 매크로는 사소한 클래스에서 작동합니다.
dirkgently

필자의 경우 버스 또는 자동차를 만들려면 다른 정의가 필요하지 않습니다. 그렇지 않으면 팩토리 디자인 패턴을 사용하지 않을 것입니다. 나의 목표는 공장을 가능한 한 바보로 만드는 것이었다. 그러나 나는 탈출이 :-)이 없다는 것을 여기에서 볼
갈 골드만
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.