我正在寻找一种直观且可扩展的方法来为c++中的给定基类的子类实现工厂。我想在库中提供这样的工厂函数。棘手的部分是我希望工厂也为用户定义的子类工作(例如,让库的工厂函数根据链接到它的模块构建不同的子类)。目标是让下游开发人员使用工厂的负担最小/混乱。
我想要做的一个例子是:给定std::istream
,构造并返回与内容匹配的子类的对象,如果没有找到匹配则返回空指针。全球工厂的签名如下:
Base* Factory(std::istream &is){ ... };
我熟悉prototype factories,但我更愿意避免制作/存储原型对象。相关问题已发布在此处java:Allowing maximal flexibly/extensibility using a factory。
我目前不是在寻找c++11具体的解决方案,但如果它们更优雅,我会很乐意了解这些。
我提出了一个工作解决方案,我相信它相当优雅,我将作为答案发布。我可以想象这个问题相当普遍,所以我想知道是否有人知道更好的方法。
编辑:似乎有些澄清是为了......
这个想法是工厂构造一个派生类的对象,而不包含决定哪一个的逻辑。更糟糕的是,工厂方法最终将作为库的一部分,派生类可以在插件中定义。
派生类必须能够根据提供的输入(例如输入文件)自行决定它们是否适合构造。这个决定可以作为一个可供工厂使用的谓词来实现,正如几个人所建议的那样(顺便说一句,很棒的建议!)。
答案 0 :(得分:9)
如果我理解正确,我们需要一个工厂函数,可以根据构造函数输入选择要实例化的派生类。这是迄今为止我能提出的最通用的解决方案。您可以指定映射输入以组织工厂函数,然后可以在工厂调用时指定构造函数输入。我不愿意说代码解释的比我说的要多,但我认为FactoryGen.h
和Base.h
中Derived.h
的示例实现在评论的帮助下足够清晰。如有必要,我可以提供更多细节。
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
}
我认为这是一个非常灵活/可扩展的工厂库。虽然它的代码不是非常直观,但我认为使用它非常简单。当然,我的看法是有偏见的,因为我是写作者,所以如果相反,请告诉我。
编辑:清理FactoryGen.h文件。这可能是我的最后一次更新,但这是一个有趣的练习。
答案 1 :(得分:6)
我的评论可能不太清楚。所以这里有一个依赖于模板元编程的C ++ 11“解决方案”:(虽然可能不是最好的方法)
#include <iostream>
#include <utility>
// Type list stuff: (perhaps use an existing library here)
class EmptyType {};
template<class T1, class T2 = EmptyType>
struct TypeList
{
typedef T1 Head;
typedef T2 Tail;
};
template<class... Etc>
struct MakeTypeList;
template <class Head>
struct MakeTypeList<Head>
{
typedef TypeList<Head> Type;
};
template <class Head, class... Etc>
struct MakeTypeList<Head, Etc...>
{
typedef TypeList<Head, typename MakeTypeList<Etc...>::Type > Type;
};
// Calling produce
template<class TList, class BaseType>
struct Producer;
template<class BaseType>
struct Producer<EmptyType, BaseType>
{
template<class... Args>
static BaseType* Produce(Args... args)
{
return nullptr;
}
};
template<class Head, class Tail, class BaseType>
struct Producer<TypeList<Head, Tail>, BaseType>
{
template<class... Args>
static BaseType* Produce(Args... args)
{
BaseType* b = Head::Produce(args...);
if(b != nullptr)
return b;
return Producer<Tail, BaseType>::Produce(args...);
}
};
// Generic AbstractFactory:
template<class BaseType, class Types>
struct AbstractFactory {
typedef Producer<Types, BaseType> ProducerType;
template<class... Args>
static BaseType* Produce(Args... args)
{
return ProducerType::Produce(args...);
}
};
class Base {}; // Example base class you had
struct Derived0 : public Base { // Example derived class you had
Derived0() = default;
static Base* Produce(int value)
{
if(value == 0)
return new Derived0();
return nullptr;
}
};
struct Derived1 : public Base { // Another example class
Derived1() = default;
static Base* Produce(int value)
{
if(value == 1)
return new Derived1();
return nullptr;
}
};
int main()
{
// This will be our abstract factory type:
typedef AbstractFactory<Base, MakeTypeList<Derived0, Derived1>::Type> Factory;
Base* b1 = Factory::Produce(1);
Base* b0 = Factory::Produce(0);
Base* b2 = Factory::Produce(2);
// As expected b2 is nullptr
std::cout << b0 << ", " << b1 << ", " << b2 << std::endl;
}
优点:
缺点:
最后,使用原型设计模式可能会更好。我不知道,因为我没有尝试使用我的代码。
我想陈述一些额外的事情(在进一步讨论聊天之后):
Produce
(静态)成员函数。当且仅当它们不是nullptr时才存储对象。 答案 2 :(得分:4)
更新:这个答案假设某种magic存在,可以被读取并传递给工厂,但事实并非如此。我在这里留下答案是因为a)我可以更新它,b)我还是喜欢它。
与你自己的答案没有太大的不同,没有使用C ++ 11技术(我还没有机会更新它,或者让它返回智能指针等),而不是我自己的工作,但是这是我使用的工厂类。重要的是(恕我直言)它没有调用每个可能的类的方法来找到匹配的方法 - 它通过地图来实现。
#include <map>
// extraneous code has been removed, such as empty constructors, ...
template <typename _Key, typename _Base, typename _Pred = std::less<_Key> >
class Factory {
public:
typedef _Base* (*CreatorFunction) (void);
typedef std::map<_Key, CreatorFunction, _Pred> _mapFactory;
// called statically by all classes that can be created
static _Key Register(_Key idKey, CreatorFunction classCreator) {
get_mapFactory()->insert(std::pair<_Key, CreatorFunction>(idKey, classCreator));
return idKey;
}
// Tries to create instance based on the key
static _Base* Create(_Key idKey) {
_mapFactory::iterator it = get_mapFactory()->find(idKey);
if (it != get_mapFactory()->end()) {
if (it->second) {
return it->second();
}
}
return 0;
}
protected:
static _mapFactory * get_mapFactory() {
static _mapFactory m_sMapFactory;
return &m_sMapFactory;
}
};
要使用它,您只需声明基类型,并为每个类将其注册为静态。请注意,当您注册时,键会被返回,所以我倾向于将其添加为类的成员,但这不是必需的,只是整齐:) ...
// shape.h
// extraneous code has been removed, such as empty constructors, ...
// we also don't technically need the id() method, but it could be handy
// if at a later point you wish to query the type.
class Shape {
public:
virtual std::string id() const = 0;
};
typedef Factory<std::string, Shape> TShapeFactory;
现在我们可以创建一个新的派生类,并通过TShapeFactory
...
// cube.h
// extraneous code has been removed, such as empty constructors, ...
class Cube : public Shape {
protected:
static const std::string _id;
public:
static Shape* Create() {return new Cube;}
virtual std::string id() const {return _id;};
};
// cube.cpp
const std::string Cube::_id = TShapeFactory::Register("cube", Cube::Create);
然后我们可以根据这个字符串创建一个新项目:
Shape* a_cube = TShapeFactory::Create("cube");
Shape* a_triangle = TShapeFactory::Create("triangle");
// a_triangle is a null pointer, as we've not registered a "triangle"
这种方法的优点是,如果你创建一个新的派生的,工厂可生成的类,你不需要改变任何其他代码,只要你可以看到工厂类并从基础派生:
// sphere.h
// extraneous code has been removed, such as empty constructors, ...
class Sphere : public Shape {
protected:
static const std::string _id;
public:
static Shape* Create() {return new Sphere;}
virtual std::string id() const {return _id;};
};
// sphere.cpp
const std::string Sphere::_id = TShapeFactory::Register("sphere", Sphere::Create);
我将留给读者的可能改进包括向typedef _Base base_class
添加Factory
之类的内容,以便在宣布自定义工厂时,可以使您的类派生自{{ 1}},等等。工厂也应该检查钥匙是否已经存在,但是再次......它仍然是一个练习。
答案 3 :(得分:3)
我目前可以想到的最佳解决方案是使用Factory
类,该类存储指向每个派生类生成函数的指针。当创建新的派生类时,可以在工厂中存储指向生产方法的函数指针。
以下是一些代码来说明我的方法:
#include <iostream>
#include <vector>
class Base{};
// Factory class to produce Base* objects from an int (for simplicity).
// The class uses a list of registered function pointers, which attempt
// to produce a derived class based on the given int.
class Factory{
public:
typedef Base*(*ReadFunPtr)(int);
private:
static vector<ReadFunPtr> registeredFuns;
public:
static void registerPtr(ReadFunPtr ptr){ registeredFuns.push_back(ptr); }
static Base* Produce(int value){
Base *ptr=NULL;
for(vector<ReadFunPtr>::const_iterator I=registeredFuns.begin(),E=registeredFuns.end();I!=E;++I){
ptr=(*I)(value);
if(ptr!=NULL){
return ptr;
}
}
return NULL;
}
};
// initialize vector of funptrs
std::vector<Factory::ReadFunPtr> Factory::registeredFuns=std::vector<Factory::ReadFunPtr>();
// An example Derived class, which can be produced from an int=0.
// The producing method is static to avoid the need for prototype objects.
class Derived : public Base{
private:
static Base* ProduceDerivedFromInt(int value){
if(value==0) return new Derived();
return NULL;
}
public:
Derived(){};
// registrar is a friend because we made the producing function private
// this is not necessary, may be desirable (e.g. encapsulation)
friend class DerivedRegistrar;
};
// Register Derived in the Factory so it will attempt to construct objects.
// This is done by adding the function pointer Derived::ProduceDerivedFromInt
// in the Factory's list of registered functions.
struct DerivedRegistrar{
DerivedRegistrar(){
Factory::registerPtr(&(Derived::ProduceDerivedFromInt));
}
} derivedregistrar;
int main(){
// attempt to produce a Derived object from 1: should fail
Base* test=Factory::Produce(1);
std::cout << test << std::endl; // outputs 0
// attempt to produce a Derived object from 0: works
test=Factory::Produce(0);
std::cout << test << std::endl; // outputs an address
}
TL; DR :在这种方法中,下游开发人员需要将派生类的生成函数实现为static
成员函数(或非成员函数)并注册它在工厂使用简单的struct
。
这看起来很简单,不需要任何原型对象。
答案 4 :(得分:3)
这是一个可持续的习惯用法,用于管理在运行时解决的工厂。我过去曾用它来支持相当复杂的行为。我赞成简单性和可维护性,而不会放弃功能性的方式。
TLDR:
使用运行时工厂
以下是此工厂系统的用户将与之交互的基本界面。他们不应该担心工厂的细节。
class BaseObject {
public:
virtual ~BaseObject() {}
};
BaseObject* CreateObjectFromStream(std::istream& is);
顺便说一句,我建议使用引用,boost::optional
或shared_ptr
而不是原始指针。在一个完美的世界中,界面应该告诉我谁拥有这个对象。作为一个用户,我我负责删除这个指针给我?当它是shared_ptr
时,它很痛苦。
实施运行时工厂
在另一个标题中,列出管理工厂活动时范围的详细信息。
class RuntimeFactory {
public:
virtual BaseObject* create(std::istream& is) = 0;
};
void RegisterRuntimeFactory(RuntimeFactory* factory);
void UnregisterRuntimeFactory(RuntimeFactory* factory);
我认为所有这一切的重点在于使用与工厂的初始化和使用方式不同。
我们应该注意这些免费函数的调用者拥有工厂。注册管理机构不拥有它们。
这并不是绝对必要的,尽管它可以在这些工厂被销毁的时间和地点提供更多控制。重要的是当你看到“后创建”或“预先销毁”的电话时。具有这些名称的工厂方法是所有权反转的设计气味。
另外,编写另一个包装来管理工厂的生命周期也很简单。它也有助于组合,这是更好的。
注册新工厂
为每个工厂注册编写包装器。我通常将每个工厂注册放在自己的标题中。这些标题通常只是两个函数调用。
void RegisterFooFactory();
void UnregisterFooFactory();
这可能看起来有些过分,但这种勤奋使你的编译时间缩短了。
然后我的main
被缩减为一堆注册和取消注册。
#include <foo_register.h>
#include <bar_register.h>
int main(int argc, char* argv[]) {
SetupLogging();
SetupRuntimeFactory();
RegisterFooFactory();
RegisterBarFactory();
// do work...
UnregisterFooFactory();
UnregisterBarFactory();
CleanupLogging();
return 0;
}
避免静态初始陷阱
这特别避免了静态加载期间创建的对象,就像其他一些解决方案一样。这不是一起事故。
实施注册表
正如您想象的那样,实施细节相当平凡。
class RuntimeFactoryRegistry {
public:
void registerFactory(RuntimeFactory* factory) {
factories.insert(factory);
}
void unregisterFactory(RuntimeFactory* factory) {
factories.erase(factory);
}
BaseObject* create(std::istream& is) {
std::set<RuntimeFactory*>::iterator cur = factories.begin();
std::set<RuntimeFactory*>::iterator end = factories.end();
for (; cur != end; cur++) {
// reset input?
if (BaseObject* obj = (*cur)->create(is)) {
return obj;
}
}
return 0;
}
private:
std::set<RuntimeFactory*> factories;
};
这假设所有工厂都是互斥的。放宽这个假设不太可能导致表现良好的软件。我可能会亲自提出更强烈的要求,呵呵。另一种选择是返回一个对象列表。
以下实现是静态的,以简化演示。对于多线程环境,这可能是一个问题。它不必是静态的,我也不建议它应该或不应该是静态的,它就在这里。这不是讨论的主题,所以我会留下它。
这些自由函数仅作为此实现的传递函数。这使您可以对注册表进行单元测试,或者如果您愿意,可以重复使用它。
namespace {
static RuntimeFactoryRegistry* registry = 0;
} // anon
void SetupRuntimeFactory() {
registry = new RuntimeFactoryRegistry;
}
void CleanupRuntimeFactory() {
delete registry;
registry = 0;
}
BaseObject* CreateObjectFromStream(std::istream& is) {
return registry->create(is);
}
void RegisterRuntimeFactory(RuntimeFactory* factory) {
registry->registerFactory(factory);
}
void UnregisterRuntimeFactory(RuntimeFactory* factory) {
registry->unregisterFactory(factory);
}
答案 5 :(得分:1)
首先,这里没有足够的细节来形成意见,所以我只能猜测。您提供了一个具有挑战性的问题和最小的解决方案,但没有说明您的解决方案有什么问题。
我怀疑投诉的中心是重置回到了被拒绝的建筑和下面的建筑尝试之间什么都不知道。鉴于潜在的工厂数量非常大,这种重置可能会让我们分析数百或数千次相同的数据。如果这是问题,那么问题是:如何构建谓词评估阶段以限制工作量,并允许它重用以前的解析结果。
我建议让每个工厂注册: 1)采用特化参数的工厂构建器函数(示例中为iostream) 2)一组无序的布尔谓词 3)每个谓词的必需布尔值允许构造
谓词集用于创建/修改谓词树。树中的内部节点表示谓词(分支为“通过”,“失败”,并且可能“不关心”)。内部节点和叶子都保持构造函数,如果满足祖先谓词则满足这些构造函数。在遍历树时,首先查找当前级别的构造函数,然后计算谓词并遵循所需的路径。如果在该子路径上找不到解决方案,请遵循“不关心”路径。
这允许新工厂共享谓词函数。当工厂上线/下线时,可能存在很多关于管理/排序树的问题。还有可能需要在谓词中保留解析器状态数据,并在构造完成时重置。有很多未解决的问题,但这可能有助于解决您的解决方案中存在的问题。
TL:DR; 在尝试构建时创建要遍历的谓词图。
答案 6 :(得分:0)
简单的解决方案只是一个案例:
Base *create(int type, std::string data) {
switch(type) {
case 0: return new Derived1(data);
case 1: return new Derived2(data);
};
}
但是,它只是决定你想要哪种类型:
int type_of_obj(string s) {
int type = -1;
if (isderived1(s)) type=0;
if (isderived2(s)) type=1;
return type;
}
然后它只是连接两者:
Base *create_obj(string s, string data,
Base *(*fptr)(int type, string data),
int (*fptr2)(string s))
{
int type = fptr2(s);
if (type==-1) return 0;
return fptr(type, data);
}
然后它只是注册函数指针:
class Registry {
public:
void push_back(Base* (*fptr)(int type, string data),
int (*fptr2)(string s));
Base *create(string s, string data);
};
该插件将具有2个功能,以及以下内容:
void register_classes(Registry ®) {
reg.push_back(&create, &type_of_obj);
...
}
插件加载器将dlopen / dlsym register_classes函数。
(另一方面,我自己并没有使用这种插件,因为创建新的插件太多了。我有更好的方法为我的程序提供模块化。插件杀死的是什么事实上你需要修改你的构建系统来创建新的dll或shared_libs,这样做太多了 - 理想情况下,新模块只是一个类;没有任何更复杂的构建系统修改)