มีวิธีในการยกตัวอย่างวัตถุจากสตริงที่มีชื่อชั้นของพวกเขาหรือไม่?


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 และแม่ variadic ..
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 บาร์และ 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 (); ถึง base.cpp
Spencer Rose

9
คุณมั่นใจได้อย่างไรว่าDerivedB::regได้รับการเริ่มต้นจริง ความเข้าใจของฉันคือว่ามันอาจจะไม่ถูกสร้างเลยหากไม่มีฟังก์ชั่นหรือวัตถุที่กำหนดไว้ในหน่วยการแปลderivedb.cppตาม 3.6.2
musiphil

2
รักการลงทะเบียนด้วยตนเอง เพื่อรวบรวมแม้ว่าฉันต้องการ a BaseFactory::map_type * BaseFactory::map = NULL;ในไฟล์ cpp ของฉัน หากไม่มีสิ่งนี้ตัวเชื่อมโยงจะบ่นเกี่ยวกับแผนที่สัญลักษณ์ที่ไม่รู้จัก
Sven

1
น่าเสียดายที่มันใช้ไม่ได้ ในฐานะที่เป็น musiphil แล้วชี้ให้เห็นไม่ได้เริ่มต้นถ้าไม่มีฟังก์ชั่นหรืออินสแตนซ์ของมันถูกกำหนดไว้ในหน่วยการแปลDerivedB::reg derivedb.cppนั่นหมายความว่าคลาสจะไม่ลงทะเบียนจนกว่าจะเป็นจริงทันที ไม่มีใครรู้วิธีแก้ปัญหาสำหรับสิ่งนั้น?
Tomasito665

7

ไม่มีเลย ทางออกที่ฉันต้องการสำหรับปัญหานี้คือการสร้างพจนานุกรมที่แมปชื่อกับวิธีการสร้าง คลาสที่ต้องการสร้างเช่นนี้จากนั้นลงทะเบียนวิธีการสร้างด้วยพจนานุกรม นี้จะกล่าวถึงในรายละเอียดบางอย่างในหนังสือรูปแบบการขาด


5
ใครสนใจที่จะระบุรูปแบบนี้มากกว่าชี้ไปที่หนังสือเล่มนี้?
josaphatv

ฉันคิดว่าเขากำลังอ้างถึงรูปแบบรีจิสทรี
jiggunjer

2
สำหรับผู้ที่อ่านคำตอบนี้ในตอนนี้ฉันเชื่อว่าคำตอบนั้นหมายถึงการใช้รูปแบบ Factory ซึ่งเป็นการนำไปใช้ที่ใช้พจนานุกรมเพื่อกำหนดคลาสที่จะสร้างอินสแตนซ์
Grimeh


4

ฉันได้ตอบคำถาม SO อื่นเกี่ยวกับโรงงาน 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

ความชอบของฉันคือการสร้างคลาส wrapper ซึ่งซ่อนการแมปและกลไกการสร้างวัตถุ สถานการณ์ทั่วไปที่ฉันพบคือความจำเป็นในการแมปคลาสที่ได้รับที่แตกต่างกันของคลาสฐานกับคีย์ซึ่งคลาสที่ได้รับทั้งหมดมีลายเซ็นคอนสตรัคเตอร์ทั่วไป นี่คือวิธีแก้ปัญหาที่ฉันเกิดขึ้นจนถึงตอนนี้

#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

โดยทั่วไปฉันไม่เห็นด้วยกับการใช้งานแมโครมาก แต่ฉันได้ทำข้อยกเว้นที่นี่ รหัสด้านบนสร้าง GENERIC_FACTORY_MAX_ARITY + รุ่นที่ชื่อ GenericFactory_N + 1 สำหรับแต่ละ N ระหว่าง 0 ถึง GENERIC_FACTORY_MAX_ARITY

การใช้แม่แบบคลาสที่สร้างนั้นเป็นเรื่องง่าย สมมติว่าคุณต้องการให้โรงงานสร้างวัตถุที่ได้รับ 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 คลาส destructor เป็นเสมือนเพื่ออนุญาตดังต่อไปนี้

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

การสะท้อนความหมายเช่นเดียวกับใน Java มีข้อมูลบางอย่างที่นี่: http://msdn.microsoft.com/en-us/library/y0114hz2(VS.80).aspx

โดยทั่วไปให้ค้นหา google สำหรับ "c ++ reflection"


สิ่งบนหน้าเว็บที่คุณอ้างถึงเป็นอย่างมากที่ห่างไกลจากมาตรฐาน C ++

1

Tor Brede Vekterli เป็นส่วนเสริมที่ให้ประโยชน์การใช้งานที่คุณต้องการอย่างแท้จริง ขณะนี้มันค่อนข้างเหมาะสมกับการเพิ่ม libs ปัจจุบันเล็กน้อย แต่ฉันสามารถใช้กับ 1.48_0 ได้หลังจากเปลี่ยนเนมสเปซพื้นฐาน

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

คำตอบสำหรับผู้ที่ถามว่าทำไมเรื่องแบบนี้ (เป็นภาพสะท้อน) จะมีประโยชน์สำหรับ c ++ - ฉันใช้เพื่อโต้ตอบระหว่าง UI และเอ็นจิ้น - ผู้ใช้เลือกตัวเลือกใน UI และเอ็นจินใช้สตริงการเลือก UI และสร้างวัตถุประเภทที่ต้องการ

ประโยชน์หลักของการใช้เฟรมเวิร์กที่นี่ (การบำรุงรักษารายการผลไม้อยู่ที่ใดที่หนึ่ง) คือฟังก์ชันการลงทะเบียนอยู่ในคำจำกัดความของแต่ละคลาส (และต้องการรหัสหนึ่งบรรทัดที่เรียกฟังก์ชันการลงทะเบียนต่อคลาสที่ลงทะเบียนเท่านั้น) รายการผลไม้ซึ่งจะต้องเพิ่มด้วยตนเองทุกครั้งที่ได้รับคลาสใหม่

ฉันทำให้โรงงานเป็นสมาชิกแบบคงที่ของคลาสพื้นฐานของฉัน


0

นี่คือรูปแบบจากโรงงาน ดูวิกิพีเดีย (และตัวอย่างนี้ ) คุณไม่สามารถสร้างประเภทต่อ se จากสตริงโดยไม่มีการแฮ็กอย่างมหันต์ ทำไมคุณต้องการสิ่งนี้


ฉันต้องการสิ่งนี้เพราะฉันอ่านสตริงจากไฟล์และถ้าฉันมีสิ่งนี้ฉันสามารถมีโรงงานทั่วไปดังนั้นมันไม่จำเป็นต้องรู้อะไรเลยเพื่อสร้างตัวอย่างที่ถูกต้อง มันมีพลังมาก
Gal Goldman

ดังนั้นคุณจะบอกว่าคุณไม่ต้องการคำจำกัดความของคลาสที่แตกต่างกันสำหรับรถบัสและรถยนต์เนื่องจากเป็นทั้งยานพาหนะ อย่างไรก็ตามหากคุณเพิ่มบรรทัดใหม่ไม่น่าจะมีปัญหา :) แนวทางแผนที่มีปัญหาเดียวกัน - คุณอัพเดทเนื้อหาแผนที่ แมโครสิ่งที่ใช้งานได้สำหรับชั้นเรียนเล็กน้อย
dirkgently

ฉันกำลังพูดว่าเพื่อที่จะสร้างรถบัสหรือรถยนต์ในกรณีของฉันฉันไม่ต้องการคำจำกัดความที่แตกต่างกันมิฉะนั้นรูปแบบการออกแบบจากโรงงานจะไม่ถูกใช้งาน เป้าหมายของฉันคือให้โรงงานงี่เง่าเท่าที่จะเป็นไปได้ แต่ผมเห็นที่นี่ว่าไม่มีการหลบหนี :-)
Gal โกลด์แมน
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.