वहाँ एक स्ट्रिंग से वस्तुओं को पलटने का एक तरीका है उनका वर्ग नाम?


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.*/

क्या किसी तरह से इस स्ट्रिंग को वास्तविक प्रकार (वर्ग) में परिवर्तित करने का एक तरीका है, ताकि बेसफैक्ट को सभी संभव व्युत्पन्न वर्गों को जानना न पड़े, और उनमें से प्रत्येक के लिए () हो? क्या मैं इस तार से एक कक्षा उत्पन्न कर सकता हूं?

मुझे लगता है कि यह रिफ्लेक्शन के माध्यम से C # में किया जा सकता है। क्या C ++ में भी कुछ ऐसा ही है?


C ++ 0x और वैरेडिक टेम्प्लेट के साथ इसका आंशिक रूप से संभव ..
स्मरलिन

जवाबों:


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, ...>बदले के बदले प्रकार दे सकते हैं । जैसे अगर आपके पास एक क्लास फू, बार और बाज है, तो यह इस तरह दिखता है:

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
यह विचार पसंद आया कि व्युत्पन्न वर्ग खुद को पंजीकृत करेंगे। यह वही है जो मैं देख रहा था, कारखाने से व्युत्पन्न वर्गों के कठिन-कोडित ज्ञान को हटाने का एक तरीका है।
गॉल गोल्डमैन

1
मूल रूप से किसी और प्रश्न में किसी दिन पोस्ट किया गया, यह कोड VS2010 पर मेक_पेयर के कारण अस्पष्ट टेम्पलेट त्रुटियों के साथ विफल हो जाता है। ठीक करने के लिए, make_pair को std :: pair <std :: string, Base * ()> में बदलें और इससे उन त्रुटियों को ठीक करना चाहिए। मुझे कुछ लिंकिंग त्रुटियां भी मिलीं जो बेसफैक्टिंग को जोड़कर तय की गईं :: map_type BaseFactory :: map = new map_type (); to आधार। सी सी पी
स्पेंसर रोज

9
आप यह कैसे सुनिश्चित करते हैं कि DerivedB::regवास्तव में आरंभिक है? मेरी समझ यह है कि इसका निर्माण तब नहीं किया जा सकता है derivedb.cpp, जब अनुवाद इकाई में कोई फ़ंक्शन या ऑब्जेक्ट परिभाषित न हो , 3.6.2 के अनुसार।
मुसीफिल

2
स्व-पंजीकरण से प्यार करें। संकलन करने के लिए हालांकि मुझे BaseFactory::map_type * BaseFactory::map = NULL;अपनी cpp फ़ाइल में एक की आवश्यकता थी । इसके बिना, लिंकर ने अज्ञात प्रतीक मानचित्र के बारे में शिकायत की।
स्वेग

1
दुर्भाग्य से, यह काम नहीं करता है। जैसा कि मुसिफिल ने पहले ही बताया है, DerivedB::regयदि इसके किसी भी प्रकार या उदाहरण को अनुवाद इकाई में परिभाषित नहीं किया गया है, तो इसे प्रारंभ नहीं किया जाता है derivedb.cpp। इसका मतलब यह है कि यह वर्ग वास्तव में तत्काल अस्तित्व में नहीं है। किसी को भी इसके लिए एक समाधान पता है?
टॉमसिटो 665

7

नहीं वहाँ नहीं है। इस समस्या का मेरा पसंदीदा समाधान एक शब्दकोश बनाना है जो नाम को निर्माण विधि के लिए तैयार करता है। कक्षाएं जो इस तरह से बनाना चाहते हैं, फिर शब्दकोश के साथ एक निर्माण विधि पंजीकृत करें। यह गोफ पैटर्न बुक में कुछ विस्तार से चर्चा की गई है ।


5
कोई भी इस बात की पहचान करने के लिए परवाह करता है कि पुस्तक में केवल बिंदु के बजाय यह कौन सा पैटर्न है?
जोसफातव

मुझे लगता है कि वह रजिस्ट्री पैटर्न की बात कर रहा है।
जिग्गंजर

2
अब इस उत्तर को पढ़ने वालों के लिए, मेरा मानना ​​है कि उत्तर फैक्ट्री पैटर्न का उपयोग करने की बात कर रहा है, एक कार्यान्वयन जो डिक्शनरी का उपयोग करके यह निर्धारित करता है कि किस वर्ग को त्वरित करना है।
ग्रिम्ह


4

मैंने C ++ कारखानों के बारे में एक और SO प्रश्न में उत्तर दिया है। कृपया वहाँ देखें क्या कोई लचीली फैक्ट्री ब्याज की है। मैं मैक्रो का उपयोग करने के लिए ईटी ++ से एक पुराने तरीके का वर्णन करने की कोशिश करता हूं जिसने मेरे लिए बहुत अच्छा काम किया है।

ET ++ पुराने MacApp को C ++ और X11 पोर्ट करने का प्रोजेक्ट था। इसके प्रयास में एरिक गामा आदि ने डिज़ाइन पैटर्न के बारे में सोचना शुरू किया


2

बूस्ट :: फंक्शनल में एक फैक्ट्री टेम्प्लेट है जो काफी लचीला है: 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 समावेशी है।

उत्पन्न वर्ग टेम्पलेट का उपयोग करना आसान है। मान लीजिए कि आप एक स्ट्रिंग मैपिंग का उपयोग करके बेसक्लास व्युत्पन्न वस्तुओं को बनाने के लिए एक कारखाना चाहते हैं। व्युत्पन्न वस्तुओं में से प्रत्येक 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;
}

इसे संकलित करें और चलाएँ (ग्रहण के साथ ऐसा कर चुके हैं)

आउटपुट:

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 चयन स्ट्रिंग लेता है, और इच्छित प्रकार की एक वस्तु का उत्पादन करता है।

यहां फ्रेमवर्क का उपयोग करने का मुख्य लाभ (कहीं-कहीं एक फल-सूची बनाए रखने पर) यह है कि रजिस्टरिंग फ़ंक्शन प्रत्येक वर्ग की परिभाषा में है (और केवल पंजीकृत कक्षा को पंजीकरण फ़ंक्शन को कॉल करने के लिए कोड की एक पंक्ति की आवश्यकता होती है) - एक फ़ाइल युक्त के विपरीत फ्रूट-लिस्ट, जिसे हर बार एक नई क्लास के निकाले जाने के समय मैन्युअल रूप से जोड़ना होगा।

मैंने कारखाने को अपने आधार वर्ग का एक स्थिर सदस्य बनाया।


0

यह कारखाना पैटर्न है। विकिपीडिया (और यह उदाहरण देखें )। आप कुछ अहंकारी हैक के बिना स्ट्रिंग से प्रति प्रकार एक प्रकार नहीं बना सकते। आप इसकी आवश्यकता क्यों है?


मुझे इसकी आवश्यकता है क्योंकि मैंने एक फ़ाइल से तार पढ़े हैं, और अगर मेरे पास यह है, तो मेरे पास कारखाना इतना सामान्य हो सकता है, कि इसे सही उदाहरण बनाने के लिए कुछ भी पता नहीं होगा। यह बहुत शक्तिशाली है।
गैल गोल्डमैन

तो, क्या आप कह रहे हैं कि आपको बस और कार के लिए अलग-अलग वर्ग की परिभाषा की आवश्यकता नहीं होगी क्योंकि वे दोनों वाहन हैं? हालाँकि, यदि आप करते हैं, तो दूसरी पंक्ति को जोड़ने से वास्तव में कोई समस्या नहीं होनी चाहिए :) मानचित्र के दृष्टिकोण में एक ही समस्या है - आप नक्शे की सामग्री को अपडेट करते हैं। मैक्रो चीज़ तुच्छ वर्गों के लिए काम करती है।
dirkgently

मैं कह रहा हूं कि मेरे मामले में एक बस या कार बनाने के लिए, मुझे अलग-अलग परिभाषाओं की आवश्यकता नहीं है, अन्यथा फैक्टरी डिजाइन पैटर्न कभी भी उपयोग में नहीं होगा। मेरा लक्ष्य कारखाने को बेवकूफ बनाना था क्योंकि यह हो सकता है। लेकिन मैं यहां देख रहा हूं कि कोई बच नहीं है :-)
गैल गोल्डमैन
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.