Sınıf adlarını tutan bir dizeden nesneleri başlatmanın bir yolu var mı?


143

Bir dosyam var: Base.h

class Base;
class DerivedA : public Base;
class DerivedB : public Base;

/*etc...*/

ve başka bir dosya: 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.*/

Bir şekilde bu dizeyi gerçek bir türe (sınıf) dönüştürmenin bir yolu var mı, böylece BaseFactory olası tüm Türetilmiş sınıfları bilmek zorunda kalmayacak ve her biri için if () sahibi olacak mı? Bu dizeden bir sınıf üretebilir miyim?

Bu C # Yansıma yoluyla yapılabilir düşünüyorum. C ++ 'da benzer bir şey var mı?


C ++ 0x ve
varyasyon

Yanıtlar:


227

Haritayı kendiniz yapmadığınız sürece hayır, hiç yok. C ++ türleri çalışma zamanında belirlenen nesneler oluşturmak için bir mekanizmaya sahip değildir. Yine de, bu haritayı kendiniz yapmak için bir harita kullanabilirsiniz:

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>;

Ve sonra yapabilirsin

return map[some_string]();

Yeni bir örnek almak. Başka bir fikir, türlerin kendilerini kaydettirmektir:

// 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");

Kayıt için bir makro oluşturmaya karar verebilirsiniz

#define REGISTER_DEC_TYPE(NAME) \
    static DerivedRegister<NAME> reg

#define REGISTER_DEF_TYPE(NAME) \
    DerivedRegister<NAME> NAME::reg(#NAME)

Eminim bu ikisi için daha iyi isimler var. Burada kullanılması muhtemelen mantıklı olan başka bir şey shared_ptr.

Ortak taban sınıfı olmayan ilişkisiz türler kümeniz varsa, işlev işaretçisine boost::variant<A, B, C, D, ...>bunun yerine bir dönüş türü verebilirsiniz . Bir Foo, Bar ve Baz sınıfınız varsa, şöyle görünür:

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::variantbir birlik gibidir. Hangi türün başlangıçta veya atamak için kullanıldığına bakarak hangi tipte depolandığını bilir. Buradaki belgelerine bir göz atın . Son olarak, ham işlev işaretçisinin kullanımı da biraz eskidir. Modern C ++ kodu belirli işlevlerden / türlerden ayrılmalıdır. Daha Boost.Functioniyi bir yol aramak için bakmak isteyebilirsiniz . O zaman şöyle görünecektir (harita):

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

std::functionC ++ 'ın sonraki sürümünde de mevcut olacak std::shared_ptr.


3
Türetilmiş sınıfların kendilerini kaydettireceği fikrini çok sevdik. Tam olarak aradığım şey, türetilmiş sınıfların fabrikadan var olduğu sabit kodlanmış bilgiyi kaldırmanın bir yolu.
Gal Goldman

1
Başlangıçta somedave tarafından başka bir soruda yayınlanan bu kod VS2010'da make_pair nedeniyle belirsiz şablon hatalarıyla başarısız oluyor. Düzeltmek için make_pair öğesini std :: pair <std :: string, Base * ( ) ()> olarak değiştirin; bu hataları düzeltmesi gerekir. Ayrıca BaseFactory :: map_type BaseFactory :: map = new map_type () eklenerek giderilen bazı bağlantı hataları var ; cpp'e
Spencer Rose

9
Bunun DerivedB::regbaşlatılmasını nasıl sağlıyorsunuz? Anladığım kadarıyla, derivedb.cpp3.6.2'ye göre çeviri biriminde herhangi bir işlev veya nesne tanımlanmamışsa, hiç yapılandırılmayabilir .
musiphil

2
Kendi kendine kaydı seviyorum. Derlemek için olsa da BaseFactory::map_type * BaseFactory::map = NULL;benim cpp dosyasında bir ihtiyaç vardı . Bu olmadan, bağlayıcı bilinmeyen sembol haritasından şikayet etti.
Sven

1
Ne yazık ki, bu işe yaramıyor. Musifil'in daha önce işaret DerivedB::regettiği gibi, çeviri biriminde işlevlerinden veya örneklerinden hiçbiri tanımlanmamışsa başlatılmaz derivedb.cpp. Bu, sınıfın gerçekte oturum açılıncaya kadar kayıtlı olmadığı anlamına gelir. Bunun için bir çözüm bilen var mı?
Tomasito665

7

Hayır yok. Bu soruna tercih ettiğim çözüm, adı oluşturma yöntemiyle eşleştiren bir sözlük oluşturmaktır. Bunun gibi oluşturulması istenen sınıflar daha sonra sözlüğe bir oluşturma yöntemi kaydeder. Bu, GoF kalıpları kitabında bazı ayrıntılarda tartışılmıştır .


5
Kimse bunun kitabın üzerine gelmekten ziyade hangi model olduğunu belirlemeyi umursa?
josaphatv

Bence o kayıt desenine atıfta bulunuyor.
jiggunjer

2
Şimdi bu cevabı okuyanlar için, cevabın, hangi sınıfın somutlaştırılacağını belirlemek için bir sözlük kullanan bir uygulama olan Factory modelini kullanmak olduğuna inanıyorum.
Grimeh


4

C ++ fabrikaları hakkında başka bir SO sorusuna cevap verdim. Esnek bir fabrikanın ilgilenip ilgilenmediğini görmek için lütfen buraya bakın . Benim için harika çalıştı makroları kullanmak için ET ++ eski bir yol tarif etmeye çalışın.

ET ++ , eski MacApp'ı C ++ ve X11'e taşıyan bir projeydi. Bu çabada Eric Gamma vs Tasarım Desenlerini düşünmeye başladı


2

boost :: function, oldukça esnek bir fabrika şablonuna sahiptir: http://www.boost.org/doc/libs/1_54_0/libs/functional/factory/doc/html/index.html

Benim tercihim, eşleme ve nesne oluşturma mekanizmasını gizleyen sarıcı sınıflar oluşturmaktır. Karşılaştığım yaygın senaryo, bazı temel sınıfların farklı türetilmiş sınıflarını, türetilmiş sınıfların ortak bir kurucu imzasına sahip olduğu anahtarlarla eşleme gereksinimidir. İşte şimdiye kadar bulduğum çözüm.

#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

Genel olarak ağır makro kullanımına karşıyım ama burada bir istisna yaptım. Yukarıdaki kod, GenericFactory_N adlı bir sınıfın GENERIC_FACTORY_MAX_ARITY + 1 sürümlerini oluşturur; her N için 0 ile GENERIC_FACTORY_MAX_ARITY dahil.

Oluşturulan sınıf şablonlarını kullanmak kolaydır. Bir fabrikanın, dize eşlemesi kullanarak BaseClass türetilmiş nesneleri oluşturmasını istediğinizi varsayalım. Türetilen nesnelerin her biri yapıcı parametresi olarak 3 tamsayı alır.

#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 sınıfı yıkıcı aşağıdakilere izin vermek için sanaldır.

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;

Genel fabrika üreteci makrosunun bu satırının

#define BOOST_PP_FILENAME_1 "GenericFactory.hpp"

Genel fabrika başlık dosyasının GenericFactory.hpp olarak adlandırıldığını varsayar


2

Nesneleri kaydetmek ve bunlara dize adlarıyla erişmek için ayrıntılı çözüm.

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;
}

Derleme ve Çalıştırma (Eclipse ile yaptık)

Çıktı:

Base constructor
test1 constructor m_a=10m_b=20
Base constructor
test1 constructor m_a=30m_b=40


1

Tor Brede Vekterli, tam olarak aradığınız işlevselliği sağlayan bir destek uzantısı sağlar. Şu anda, mevcut boost libs ile biraz gariptir, ancak temel ad alanını değiştirdikten sonra 1.48_0 ile çalışmayı başardım.

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

Neden böyle bir şeyin (yansıma olarak) c ++ için yararlı olacağını sorgulayanlara cevap olarak - UI ve motor arasındaki etkileşimler için kullanıyorum - kullanıcı UI'de bir seçenek seçer ve motor UI seçim dizesini alır, ve istenen tipte bir nesne üretir.

Buradaki çerçeveyi kullanmanın başlıca faydası (bir yerde bir meyve listesi bulundurmak), kayıt işlevinin, her sınıfın tanımında olması (ve her kayıtlı sınıf için kayıt işlevini çağıran bir kod satırı gerektirmesidir) - içeren bir dosyanın aksine her yeni sınıf türetildiğinde el ile eklenmesi gereken meyve listesi.

Fabrikayı temel sınıfımın statik bir üyesi yaptım.


0

Bu fabrika modelidir. Bkz. Wikipedia (ve bu örnek). Bazı hileli kesmek olmadan bir dizeden kendiniz bir tür oluşturamazsınız. buna ne için ihtiyacın var?


Bir dosyadan dizeleri okuduğum için buna ihtiyacım var ve eğer buna sahipsem, o zaman fabrikayı o kadar genel yapabilirim ki, doğru örneği oluşturmak için hiçbir şey bilmek zorunda kalmayacaktım. Bu çok güçlü.
Gal Goldman

Yani, her ikisi de Araç olduğu için Otobüs ve Araba için farklı sınıf tanımlarına ihtiyacınız olmayacağını mı söylüyorsunuz? Ancak, bunu yaparsanız, başka bir satır eklemek gerçekten sorun olmamalı :) Harita yaklaşımı aynı soruna sahiptir - harita içeriğini güncellersiniz. Makro şey önemsiz sınıflar için çalışır.
dirkgently

Benim durumumda bir Otobüs veya Araba YARATMAK için farklı tanımlara ihtiyacım olmadığını söylüyorum, aksi takdirde Fabrika tasarım deseninin asla kullanılmayacağını. Amacım fabrikayı olabildiğince aptallaştırmaktı. Ama burada kaçış olmadığını görüyorum :-)
Gal Goldman
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.