在C ++中实现可扩展工厂的优雅方式 [英] Elegant way to implement extensible factories in C++

查看:112
本文介绍了在C ++中实现可扩展工厂的优雅方式的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在寻找一种直观和可扩展的方法来实现 c ++ 。我想在库中提供这样的工厂函数。棘手的部分是,我想要工厂用于用户定义的子类(例如让库的工厂函数构建不同的子类,取决于链接到它的模块)。目标是为下游开发人员使用工厂最小的负担/混乱。



我想做的一个例子是:给定一个 std :: istream ,构造并返回任何子类匹配内容的对象,如果没有匹配,则返回空指针。全局工厂将有一个签名如:

  Base * Factory(std :: istream& is){...} ; 

我熟悉原型工厂,但我更喜欢避免制作/存储原型对象的需要。相关问题发布在此处 java 的问题:使用工厂允许最大的灵活性/可扩展性



我不是在寻找 c ++ 11 - 目前特定的解决方案,但如果他们更优雅,我很乐意了解这些。



我想出了一个工作解决方案,我相信是相当优雅,我将发布为答案。我可以想象这个问题是相当普遍,所以我想知道是否有人知道更好的方法。



EDIT :似乎有些澄清是为了...



想法是为工厂构造一个派生类的对象,而不包含决定哪个逻辑的逻辑。更糟糕的是,工厂方法将最终作为库的一部分,派生类可以在插件中定义。



派生类必须能够根据提供的输入(例如输入文件)自行决定它们是否适合构造。这个决定可以被实现为一个可以被工厂使用的谓词,正如几个人所建议的(很好的建议,顺便!)。

解决方案

如果我理解这正确,我们想要一个工厂函数,可以选择哪个派生类基于构造函数输入实例化。这是我可以想出到目前为止最通用的解决方案。您可以指定映射输入以组织工厂函数,然后可以在工厂调用时指定构造函数输入。我不喜欢说代码解释比我能说的更多,但我认为 FactoryGen.h Base.h code>和 Derived.h 足够清楚。

 如果需要,我可以提供更多详细信息。 #pragma once 

#include< map>
#include< tuple>
#include< typeinfo>

// C ++ 11 typename别名,在visual studio中不起作用...
/ *
template< typename Base>
使用FactoryGen< Base> = FactoryGen< Base,void> ;;
* /

//为此地图中的所有类分配唯一的ID。比typeid(类).hash_code()更好,因为在运行时没有计算。
size_t__CLASS_UID = 0;

template< typename T>
inline size_t __GET_CLASS_UID(){
static const size_t id = __CLASS_UID ++;
return id;
}

//这些是来自工厂及其专业化的常见代码片段。
template< typename Base>
struct FactoryGenCommon {
typedef std :: pair< void *,size_t>厂; //一个工厂是一个函数指针及其唯一的类型标识符

//生成函数指针类型,使我没有愚蠢的查找typedefs到处
template< typename ... InArgs> ;
struct FPInfo {//代表函数指针信息
typedef Base *(* Type)(InArgs ...);
};

//检查工厂是否不为null并匹配它的签名(有助于确保工厂实际上接受指定的输入)
template< typename ... InArgs>
static bool isValid(const Factory& factory){
auto maker = factory.first;
if(maker == nullptr)return false;

//我们必须检查Factory是否会接受这些inArgs
auto type = factory.second;
auto intype = __GET_CLASS_UID< FPInfo< InArgs ...>>();
if(intype!= type)return false;

return true;
}
};

//模板输入是工厂返回的基本类型,而Args ...将决定函数指针的索引。
template< typename Base,typename ... Args>
struct FactoryGen:FactoryGenCommon< Base> {
typedef std :: tuple< Args ...>元组;
typedef std :: map< Tuple,Factory>地图; // the Args ... are keys to a map of function pointers

inline static Map& get(){
static Map factoryMap;
return factoryMap;
}

template< typename ... InArgs>
static void add(void * factory,const Args& ... args){
Tuple selTuple = std :: make_tuple(args ...); // selTuple表示选择元组。这个元组是映射的键,它给我们一个函数指针
get()[selTuple] = Factory(factory,__ GET_CLASS_UID< FPInfo< InArgs ...>
}

template< typename ... InArgs>
static Base * make(const Args& ... args,const InArgs& ... inArgs){
Factory factory = get()[std :: make_tuple(args ...)];
if(!isValid< InArgs ...>(factory))return nullptr;
return((FPInfo< InArgs ...> :: Type)factory.first)(inArgs ...);
}
};

//专用于没有选择映射的工厂
template< typename Base>
struct FactoryGen< Base,void> :FactoryGenCommon< Base> {
inline static Factory& get(){
static工厂工厂;
return factory;
}

template< typename ... InArgs>
static void add(void * factory){
get()= Factory(factory,__ GET_CLASS_UID< FPInfo< InArgs ...>>());
}

template< typename ... InArgs>
static Base * make(const InArgs& ... inArgs){
Factory factory = get();
if(!isValid< InArgs ...>(factory))return nullptr;
return((FPInfo< InArgs ...> :: Type)factory.first)(inArgs ...);
}
};

//这调用函数initialize()函数来注册每个类与相应的工厂(即使一个类尝试多次初始化)ONCE
//这一步可能被绕过,但我不完全确定
模板< class T>
class RegisterInit {
int& count(void){static int x = 0; return x; } //计算每个派生的调用者数量
public:
RegisterInit(void){
if((count())++ == 0){//只在第一个该类的调用者T
T :: initialize();
}
}
};

Base.h

  #pragma once 

#include< map>
#include< string>
#include< iostream>
#includeProcedure.h
#includeFactoryGen.h

class Base {
public:
static Base * makeBase(){ return new Base; }
static void initialize(){FactoryGen< Base,void> :: add(Base :: makeBase); } //我们希望这是默认映射,指定它需要void输入

virtual void speak(){std :: cout< Base< std :: endl; }
};

RegisterInit& lt; Base> __基础; //为Base调用初始化

Derived.h

  #pragma once 

#includeBase.h

class Derived0:public Base {
private:
std :: string speakStr;
public:
Derived0(std :: string sayThis){speakStr = sayThis; }

static Base * make(std :: string sayThis){return new Derived0(sayThis); }
static void initialize(){FactoryGen< Base,int> :: add< std :: string>(Derived0 :: make,0); } //我们映射到这个子类通过int与0,但指定它需要一个字符串输入

virtual void speak(){std :: cout< speakStr<< std :: endl; }
};

RegisterInit< Derived0> __d0init; //为Derived0调用initialize()

类Derived1:public Base {
private:
std :: string speakStr;
public:
Derived1(std :: string sayThis){speakStr = sayThis; }

static Base * make(std :: string sayThat){return new Derived0(sayThat); }
static void initialize(){FactoryGen< Base,int> :: add< std :: string>(Derived0 :: make,1); } //我们通过int和1映射到这个子类,但是指定它需要一个字符串输入

virtual void speak(){std :: cout< speakStr<< std :: endl; }
};

RegisterInit< Derived1> __d1init; //为Derived1调用initialize()

Main.cpp

  #include< windows.h> // for Sleep()
#includeBase.h
#includeDerived.h

using namespace std;

int main(){
Base * b = FactoryGen< Base,void> :: make //没有映射,没有输入
Base * d0 = FactoryGen< Base,int> :: make< string>(0,Derived0); // int mapping,string input
Base * d1 = FactoryGen< Base,int> :: make< string>(1,I am Derived1); // int mapping,string input

b-> speak();
d0-> speak();
d1-> speak();

cout<< 基底尺寸:< sizeof(Base)< endl;
cout<< Size of Derived0:<< sizeof(Derived0)< endl;

睡眠(3000); // Windows& Visual Studio,sry
}

我认为这是一个非常灵活/可扩展的工厂库。虽然它的代码不是很直观,我认为使用它是相当简单。当然,我的观点是有偏见的,因为我是写它的一个,所以请让我知道,如果是相反。



编辑: / strong>清除FactoryGen.h文件。这可能是我最后一次更新,但这是一个有趣的练习。


I am looking for an intuitive and extensible way to implement factories for subclasses of a given base class in . I want to provide such a factory function in a library.The tricky part is that I want said factory to work for user-defined subclasses as well (e.g. having the library's factory function build different subclasses depending on what modules are linked to it). The goal is to have minimal burden/confusion for downstream developers to use the factories.

An example of what I want to do is: given a std::istream, construct and return an object of whatever subclass matches the content, or a null pointer if no matches are found. The global factory would have a signature like:

Base* Factory(std::istream &is){ ... };

I am familiar with prototype factories, but I prefer to avoid the need to make/store prototype objects. A related question is posted here for : Allowing maximal flexibly/extensibility using a factory.

I am not looking for -specific solutions at the moment, but if they are more elegant I would be happy to learn about those.

I came up with one working solution which I believe is fairly elegant, which I will post as an answer. I can imagine this problem to be fairly common, so I am wondering if anyone knows of better approaches.

EDIT: it seems some clarification is in order...

The idea is for the factory to construct an object of a derived class, without containing the logic to decide which one. To make matters worse, the factory method will end up as part of a library and derived classes may be defined in plugins.

Derived classes must be able to decide for themselves whether or not they are fit for construction, based on the input provided (for example an input file). This decision can be implemented as a predicate that can be used by the factory, as was suggested by several people (great suggestion, by the way!).

解决方案

If I understand this correctly, we want a factory function that can select which derived class to instantiate based on constructor inputs. This is the most generic solution that I could come up with so far. You specify mapping inputs to organize factory functions, and then you can specify constructor inputs upon factory invocation. I hate to say that the code explains more than I could in words, however I think the example implementations of FactoryGen.h in Base.h and Derived.h are clear enough with the help of comments. I can provide more details if necessary.

FactoryGen.h

#pragma once

#include <map>
#include <tuple>
#include <typeinfo>

//C++11 typename aliasing, doesn't work in visual studio though...
/*
template<typename Base>
using FactoryGen<Base> = FactoryGen<Base,void>;
*/

//Assign unique ids to all classes within this map.  Better than typeid(class).hash_code() since there is no computation during run-time.
size_t __CLASS_UID = 0;

template<typename T>
inline size_t __GET_CLASS_UID(){
    static const size_t id = __CLASS_UID++;
    return id;
}

//These are the common code snippets from the factories and their specializations. 
template<typename Base>
struct FactoryGenCommon{
    typedef std::pair<void*,size_t> Factory; //A factory is a function pointer and its unique type identifier

    //Generates the function pointer type so that I don't have stupid looking typedefs everywhere
    template<typename... InArgs>
    struct FPInfo{ //stands for "Function Pointer Information"
        typedef Base* (*Type)(InArgs...);
    };

    //Check to see if a Factory is not null and matches it's signature (helps make sure a factory actually takes the specified inputs)
    template<typename... InArgs>
    static bool isValid(const Factory& factory){
        auto maker = factory.first;
        if(maker==nullptr) return false;

        //we have to check if the Factory will take those inArgs
        auto type = factory.second;
        auto intype = __GET_CLASS_UID<FPInfo<InArgs...>>();
        if(intype != type) return false;

        return true;
    }
};

//template inputs are the Base type for which the factory returns, and the Args... that will determine how the function pointers are indexed.
template<typename Base, typename... Args> 
struct FactoryGen : FactoryGenCommon<Base>{
    typedef std::tuple<Args...> Tuple;
    typedef std::map<Tuple,Factory> Map; //the Args... are keys to a map of function pointers

    inline static Map& get(){ 
        static Map factoryMap;
        return factoryMap; 
    }

    template<typename... InArgs>
    static void add(void* factory, const Args&... args){
        Tuple selTuple = std::make_tuple(args...); //selTuple means Selecting Tuple.  This Tuple is the key to the map that gives us a function pointer
        get()[selTuple] = Factory(factory,__GET_CLASS_UID<FPInfo<InArgs...>>());
    }

    template<typename... InArgs>
    static Base* make(const Args&... args, const InArgs&... inArgs){
        Factory factory = get()[std::make_tuple(args...)];
        if(!isValid<InArgs...>(factory)) return nullptr;
        return ((FPInfo<InArgs...>::Type)factory.first) (inArgs...);
    }
};

//Specialize for factories with no selection mapping
template<typename Base>
struct FactoryGen<Base,void> : FactoryGenCommon<Base>{
    inline static Factory& get(){
        static Factory factory;
        return factory; 
    }

    template<typename... InArgs>
    static void add(void* factory){
        get() = Factory(factory,__GET_CLASS_UID<FPInfo<InArgs...>>());
    }

    template<typename... InArgs>
    static Base* make(const InArgs&... inArgs){
        Factory factory = get();
        if(!isValid<InArgs...>(factory)) return nullptr;
        return ((FPInfo<InArgs...>::Type)factory.first) (inArgs...);
    }
};

//this calls the function "initialize()" function to register each class ONCE with the respective factory (even if a class tries to initialize multiple times)
//this step can probably be circumvented, but I'm not totally sure how
template <class T>
class RegisterInit {
  int& count(void) { static int x = 0; return x; } //counts the number of callers per derived
public:
  RegisterInit(void) { 
    if ((count())++ == 0) { //only initialize on the first caller of that class T
      T::initialize();
    }
  }
};

Base.h

#pragma once

#include <map>
#include <string>
#include <iostream>
#include "Procedure.h"
#include "FactoryGen.h"

class Base {
public:
    static Base* makeBase(){ return new Base; }
    static void initialize(){ FactoryGen<Base,void>::add(Base::makeBase); } //we want this to be the default mapping, specify that it takes void inputs

    virtual void speak(){ std::cout << "Base" << std::endl; }
};

RegisterInit<Base> __Base; //calls initialize for Base

Derived.h

#pragma once

#include "Base.h"

class Derived0 : public Base {
private:
    std::string speakStr;
public:
    Derived0(std::string sayThis){ speakStr=sayThis; }

    static Base* make(std::string sayThis){ return new Derived0(sayThis); }
    static void initialize(){ FactoryGen<Base,int>::add<std::string>(Derived0::make,0); } //we map to this subclass via int with 0, but specify that it takes a string input

    virtual void speak(){ std::cout << speakStr << std::endl; }
};

RegisterInit<Derived0> __d0init; //calls initialize() for Derived0

class Derived1 : public Base {
private:
    std::string speakStr;
public:
    Derived1(std::string sayThis){ speakStr=sayThis; }

    static Base* make(std::string sayThat){ return new Derived0(sayThat); }
    static void initialize(){ FactoryGen<Base,int>::add<std::string>(Derived0::make,1); } //we map to this subclass via int with 1, but specify that it takes a string input

    virtual void speak(){ std::cout << speakStr << std::endl; }
};

RegisterInit<Derived1> __d1init; //calls initialize() for Derived1

Main.cpp

#include <windows.h> //for Sleep()
#include "Base.h"
#include "Derived.h"

using namespace std;

int main(){
    Base* b = FactoryGen<Base,void>::make(); //no mapping, no inputs
    Base* d0 = FactoryGen<Base,int>::make<string>(0,"Derived0"); //int mapping, string input
    Base* d1 = FactoryGen<Base,int>::make<string>(1,"I am Derived1"); //int mapping, string input

    b->speak();
    d0->speak();
    d1->speak();

    cout << "Size of Base: " << sizeof(Base) << endl;
    cout << "Size of Derived0: " << sizeof(Derived0) << endl;

    Sleep(3000); //Windows & Visual Studio, sry
}

I think this is a pretty flexible/extensible factory library. While the code for it is not very intuitive, I think using it is fairly simple. Of course, my view is biased seeing as I'm the one that wrote it, so please let me know if it is the contrary.

EDIT : Cleaned up the FactoryGen.h file. This is probably my last update, however this has been a fun exercise.

这篇关于在C ++中实现可扩展工厂的优雅方式的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆