Có cách nào để khởi tạo các đối tượng từ một chuỗi chứa tên lớp của chúng không?


143

Tôi có một tập tin: Base.h

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

/*etc...*/

và một tệp khác: 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ách nào để chuyển đổi chuỗi này thành một loại (lớp) thực tế, để BaseFactory không phải biết tất cả các lớp Derogen có thể và có if () cho mỗi một trong số chúng không? Tôi có thể tạo một lớp từ chuỗi này không?

Tôi nghĩ rằng điều này có thể được thực hiện trong C # thông qua Reflection. Có một cái gì đó tương tự trong C ++?


một phần có thể với các mẫu C ++ 0x và
matrixdic

Câu trả lời:


227

Không, không có, trừ khi bạn tự làm bản đồ. C ++ không có cơ chế để tạo các đối tượng có kiểu được xác định khi chạy. Mặc dù vậy, bạn có thể sử dụng bản đồ để thực hiện việc lập bản đồ đó:

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

Và sau đó bạn có thể làm

return map[some_string]();

Lấy một ví dụ mới. Một ý tưởng khác là để các loại đăng ký chính họ:

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

Bạn có thể quyết định tạo một macro cho đăng ký

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

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

Tôi chắc chắn có những cái tên tốt hơn cho cả hai. Một điều có lẽ có ý nghĩa để sử dụng ở đây là shared_ptr.

Nếu bạn có một tập hợp các loại không liên quan mà không có lớp cơ sở chung, bạn có thể cung cấp cho con trỏ hàm một kiểu trả về boost::variant<A, B, C, D, ...>thay thế. Giống như nếu bạn có một lớp Foo, Bar và Baz, nó trông như thế này:

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::variantgiống như một công đoàn. Nó biết loại nào được lưu trữ trong đó bằng cách tìm kiếm đối tượng nào được sử dụng để khởi tạo hoặc gán cho nó. Có một cái nhìn vào tài liệu của nó ở đây . Cuối cùng, việc sử dụng một con trỏ hàm thô cũng hơi cũ. Mã C ++ hiện đại nên được tách rời khỏi các chức năng / loại cụ thể. Bạn có thể muốn nhìn vào Boost.Functionđể tìm kiếm một cách tốt hơn. Nó sẽ trông như thế này sau đó (bản đồ):

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

std::functioncũng sẽ có sẵn trong phiên bản tiếp theo của C ++, bao gồm cả std::shared_ptr.


3
Yêu ý tưởng rằng các lớp dẫn xuất sẽ tự đăng ký. Đó chính xác là những gì tôi đang tìm kiếm, một cách để loại bỏ kiến ​​thức được mã hóa cứng trong đó các lớp dẫn xuất tồn tại từ nhà máy.
Gal Goldman

1
Ban đầu được đăng bởi somedave trong một câu hỏi khác, mã này không thành công trên VS2010 với các lỗi mẫu không rõ ràng do make_ Pair. Để sửa lỗi, thay đổi make_ Pair thành std :: cặp <std :: string, Base * ( ) ()> và nó sẽ sửa các lỗi đó. Tôi cũng đã nhận được một số lỗi liên kết đã được sửa bằng cách thêm BaseFactory :: map_type BaseFactory :: map = new map_type (); to base.cpp
Spencer Rose

9
Làm thế nào để bạn đảm bảo rằng DerivedB::regthực sự được khởi tạo? Sự hiểu biết của tôi là nó có thể không được xây dựng nếu không có chức năng hoặc đối tượng được xác định trong đơn vị dịch derivedb.cpp, theo 3.6.2.
musiphil

2
Yêu tự đăng ký. Để biên dịch mặc dù tôi cần một BaseFactory::map_type * BaseFactory::map = NULL;tệp cpp. Không có điều này, trình liên kết đã phàn nàn về bản đồ biểu tượng không xác định.
Sven

1
Thật không may, điều này không hoạt động. Như musiphil đã chỉ ra, DerivedB::regkhông được khởi tạo nếu không có chức năng hoặc thể hiện nào của nó được xác định trong đơn vị dịch derivedb.cpp. Điều đó có nghĩa là lớp không được đăng ký cho đến khi nó thực sự được khởi tạo. Có ai biết một cách giải quyết cho điều đó?
Tomasito665

7

Không, không có. Giải pháp ưa thích của tôi cho vấn đề này là tạo một từ điển ánh xạ tên cho phương thức tạo. Các lớp muốn được tạo như thế này sau đó đăng ký một phương thức tạo với từ điển. Điều này được thảo luận chi tiết trong sách mẫu của GoF .


5
Bất cứ ai quan tâm để xác định mô hình này là gì, thay vì chỉ vào cuốn sách?
josaphatv

Tôi nghĩ rằng ông đang đề cập đến các mẫu đăng ký.
jiggunjer

2
Đối với những người đọc câu trả lời này bây giờ, tôi tin rằng câu trả lời đang đề cập đến việc sử dụng mẫu Factory, một triển khai sử dụng từ điển để xác định lớp nào sẽ khởi tạo.
Grimeh


4

Tôi đã trả lời trong một câu hỏi SO khác về các nhà máy C ++. Xin vui lòng xem ở đó nếu một nhà máy linh hoạt được quan tâm. Tôi cố gắng mô tả một cách cũ từ ET ++ để sử dụng các macro đã làm việc rất tốt cho tôi.

ET ++ là một dự án chuyển MacApp cũ sang C ++ và X11. Trong nỗ lực của nó, Eric Gamma v.v ... bắt đầu nghĩ về các mẫu thiết kế


2

boost :: function có một mẫu nhà máy khá linh hoạt: http://www.boost.org/doc/libs/1_54_0/libs/feftal/factory/doc/html/index.html

Mặc dù vậy, sở thích của tôi là tạo các lớp bao bọc ẩn cơ chế tạo đối tượng và ánh xạ. Kịch bản phổ biến mà tôi gặp phải là cần ánh xạ các lớp dẫn xuất khác nhau của một số lớp cơ sở thành các khóa, trong đó tất cả các lớp dẫn xuất đều có sẵn một chữ ký hàm tạo chung. Đây là giải pháp tôi đã đưa ra cho đến nay.

#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

Tôi thường phản đối việc sử dụng macro nặng, nhưng tôi đã tạo ra một ngoại lệ ở đây. Đoạn mã trên tạo GENERIC_FACTORY_MAX_ARITY + 1 phiên bản của một lớp có tên GenericFactory_N, cho mỗi N trong khoảng từ 0 đến GENERIC_FACTORY_MAX_ARITY.

Sử dụng các mẫu lớp được tạo ra rất dễ dàng. Giả sử bạn muốn một nhà máy tạo các đối tượng dẫn xuất BaseClass bằng cách sử dụng ánh xạ chuỗi. Mỗi đối tượng dẫn xuất lấy 3 số nguyên làm tham số hàm tạo.

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

Hàm hủy của lớp GenericFactory_N là ảo để cho phép các mục sau.

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;

Lưu ý rằng dòng này của macro máy phát điện chung

#define BOOST_PP_FILENAME_1 "GenericFactory.hpp"

Giả sử tệp tiêu đề chung của nhà máy được đặt tên là GenericFactory.hpp


2

Chi tiết giải pháp cho việc đăng ký các đối tượng và truy cập chúng bằng tên chuỗi.

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

Biên dịch và chạy nó (Đã thực hiện điều này với Eclipse)

Đầu ra:

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


1

Tor Brede Vekterli cung cấp một phần mở rộng tăng cường cung cấp chính xác chức năng bạn tìm kiếm. Hiện tại, nó hơi khó xử với các lib tăng hiện tại, nhưng tôi đã có thể làm cho nó hoạt động với 1.48_0 sau khi thay đổi không gian tên cơ sở của nó.

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

Để trả lời cho những người thắc mắc tại sao một thứ như vậy (như sự phản chiếu) lại hữu ích cho c ++ - Tôi sử dụng nó cho các tương tác giữa UI và công cụ - người dùng chọn một tùy chọn trong UI và công cụ lấy chuỗi chọn UI, và tạo ra một đối tượng của loại mong muốn.

Lợi ích chính của việc sử dụng khung ở đây (duy trì danh sách trái cây ở đâu đó) là chức năng đăng ký theo định nghĩa của mỗi lớp (và chỉ yêu cầu một dòng mã gọi hàm đăng ký cho mỗi lớp đã đăng ký) - trái ngược với tệp có chứa danh sách trái cây, phải được thêm thủ công vào mỗi lần một lớp mới được dẫn xuất.

Tôi đã làm cho nhà máy trở thành một thành viên tĩnh của lớp cơ sở của tôi.


0

Đây là mô hình nhà máy. Xem wikipedia (và ví dụ này ). Bạn không thể tạo một kiểu mỗi se từ một chuỗi mà không có một số hack quá lớn. Tại sao bạn cần điều này?


Tôi cần điều này bởi vì tôi đọc các chuỗi từ một tập tin, và nếu tôi có điều này, thì tôi có thể có nhà máy chung chung đến mức nó sẽ không phải biết bất cứ điều gì để tạo ra ví dụ phù hợp. Điều này rất mạnh mẽ.
Gal Goldman

Vì vậy, bạn có nói rằng bạn sẽ không cần các định nghĩa lớp khác nhau cho Xe buýt và Xe hơi vì cả hai đều là Xe không? Tuy nhiên, nếu bạn làm thế, việc thêm một dòng khác thực sự không phải là vấn đề :) Cách tiếp cận bản đồ có cùng một vấn đề - bạn cập nhật nội dung bản đồ. Các điều vĩ mô hoạt động cho các lớp tầm thường.
dirkgently

Tôi đang nói rằng để TẠO một chiếc xe buýt hoặc xe hơi trong trường hợp của tôi, tôi không cần các định nghĩa khác nhau, nếu không thì mẫu thiết kế của Nhà máy sẽ không bao giờ được sử dụng. Mục tiêu của tôi là có nhà máy ngu ngốc nhất có thể. Nhưng tôi thấy ở đây không có lối thoát :-)
Gal Goldman
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.