按字符串名称的 C++ 通用对象工厂

Posted

技术标签:

【中文标题】按字符串名称的 C++ 通用对象工厂【英文标题】:C++ generic object factory by string name 【发布时间】:2013-10-02 21:46:04 【问题描述】:

我需要一种基于作为 std::string 传递的类名来实例化对象的方法。这现在有效,但需要概括:

void* create(std::string name) 
    if(name == "classOne") return new ClassOne();
    else if(name == "classTwo") return new ClassTwo();
    /* ... */

我没有的东西

对要实例化的类的控制:可以是 30 个方类。不得对此类进行任何更改(即基础祖先、多态创建者方法等...) 完整的类名列表:以后可能会添加更多类,并且不应对该工厂进行更改。 要实例化的类的包装器:作为前两点的结果。

其他的都可以。

最佳用例场景将是:

int main() 
    void *obj = create("classTree"); // create object based on the string name
    /* ... */
    // once we know by context which specific class we are dealing with
    ClassTree *ct = (ClassTree*)obj; // cast to appropiate class
    std::cout << ct->getSomeText() << std::endl; // use object

作为一个侧面,也许是无关紧要的说明,考虑到要实例化的对象可能来自一个类或一个结构。

附加信息

我看到需要更多上下文。这是我的特定用例,经过简化:

// registration mechanism
int main() 
    std::map< std::string, void(*func)(std::string, void*) > processors; // map of processors by class name
    processors["ClassFour"] = (void(*)(std::string, void*)) &classFourMessageProcessor; // register processor (cast needed from specific to generic)

// function receiving string messages
void externalMessageHandler(std::string msg) 
    std::string objType = extractTypeFromMessageHeader(msg); // extract type from message
    // now that we know what we are dealing with, create the specific object
    void *obj = create(objType); // << creator needed
    processors[objType](msg, obj); // dispatch message to process

// previously registered message processor
void classFourMessageProcessor(std::String msg, ClassFour *obj) 
    std::string streetAddress = msg.substr(10, 15); // knowing the kind of message we can extract information
    obj->moveTheEtherTo(streetAddress); // use the created object

附加信息

我正在使用带有最新 GNU 编译器的 C++11。

【问题讨论】:

你不只是“一旦我们知道我们正在处理哪个特定类”就实例化正确的类,然后做你会用 void* 做的事情? @Kal 在知道它是什么类之前,您可能需要使用obj 这不是正常的创建工厂的方式。由于 if 语句块很大,它不能很好地扩展。见***.com/questions/5120768/… 我不认为您可以在不修改工厂代码本身的情况下允许将其他类型添加到您的工厂中。 C++ 缺乏运行时反射。例如,与 C# 不同。 返回 void* 似乎是反 C++。你真的想至少返回一个指向某种抽象基类的指针。 【参考方案1】:

您可以为每个类类型存储一个工厂函数。一个简单的方法是使用模板

template <typename T>
void* creator() 
  return new T();

并将它们也存储在地图中(即“ClassFour”链接到 creator&lt;ClassFour&gt;ClassFourMessageProcessor)。

编辑:为澄清起见,processors 变为

typedef void* (*CreatorFunc)();
typedef void (*ProcessorFunc)(std::string, void*);

typedef std::pair<CreatorFunc, ProcessorFunc> Entry;
std::map< std::string, Entry > processors;

添加一个新类很简单

processors["SomeClass"] = Entry(creator<SomeClass>, ClassFourMessageProcessor);

【讨论】:

不确定这有什么用途。这比new ClassOne() 容易多少? 这不符合他关闭修改的要求。每个新类都需要有这个方法。 它允许工厂也保留在列表中,从而使创建函数独立于注册的类。它避免了所有支持的类类型的手写分支。这也是一个非常标准的模式。 @WilliamCustode 这个create() 不是课程的一部分。这是一个模板化的全局函数。 @WilliamCustode 它不需要更改类。它正是您的示例代码所要求的:它概括了所有支持的类的手写分支。【参考方案2】:

这里有一个:

    为每个类创建一个 createInsrance() 函数(不是方法),该函数实例化一个实例并返回一个转换为 void* 的指针。请注意,此函数不是类的一部分 - 只是一个普通函数。 维护一个字符串映射到函数指针到 createInstance 类型函数。 “注册”映射中的每个相关类 - 将字符串函数指针对添加到映射中。 现在通用 create 将在映射中搜索字符串并调用特定的 createInstane,返回新实例的 ptr。

现在您没有对类进行任何更改,并且可以在不重新编程工厂的情况下添加更多类。 您可能至少将 #1 作为模板 - 确保让编译器实例化特定的实现。

【讨论】:

应该可以,但它不像我想要的那样通用。我正在寻找一种一劳永逸的方法,然后忘记这件事。无论如何,谢谢。 假设您希望能够在以后的版本中添加新类,您希望编译器/运行时代码如何“知道”正在添加的新类的“名称”? 在这种情况下,必须为每个类定义一个函数是太大的负担,而让编译器这样做的通用方法是可取的。【参考方案3】:

也许以下带有查找表的方法将是一个不错的解决方案。

(注意:我不知道您使用的是哪个编译器,因此此解决方案适用于 c++03,如果您使用支持 c++11 的编译器,则可以使用 unordered_map 代替 map)

(注2:你也可以使用智能指针,并注意返回值,这个例子我只想展示一个方法)

#include <iostream>
#include <string>
#include <map>
#include <vector>


struct ClassMaker

    virtual void* getInstance() const = 0;
    virtual ~ClassMaker() 
;

class Factory

private:
    std::map<std::string, ClassMaker*> lookupTable;
    typedef typename std::map<std::string, ClassMaker*>::iterator Iterator;
public:
    void addClass(const std::string& key, ClassMaker* const newClassMaker)
    
        lookupTable[key] = newClassMaker;
    

    void* create(const std::string& key)
    
        void* result = NULL;
        Iterator it = lookupTable.find(key);
        
        if(it != lookupTable.end())
            result = (it->second)->getInstance();
        return result;
    

    void releaseTable()
    
        for (Iterator it = lookupTable.begin(); it != lookupTable.end(); ++it)
            delete it->second;
    
;

struct IntCreator : public ClassMaker

    void* getInstance() const
    
        return new int;
    

;

struct StringCreator : public ClassMaker

    void* getInstance() const
    
        return new std::string;
    

;



int main()

    Factory myFactory;
    myFactory.addClass("int", new IntCreator);
    myFactory.addClass("string", new StringCreator);
    
    int* myInt = reinterpret_cast<int*>(myFactory.create("int"));
    *myInt = 10;
    std::cout<< *myInt << std::endl;
    delete myInt;
    myFactory.releaseTable();
    return 0;

【讨论】:

请阅读问题描述。这不符合它。谢谢 您的“没有要点”之一是:“完整的类名列表:以后可以添加更多类,并且不应该对该工厂进行更改。”使用这种方法,工厂永远不会改变,您可以使用单个方法添加新类 它与我描述的方法相同,除了它不模板化工厂并且使用整个多态类而不是单个工厂函数(这使事情变得复杂,因为它需要对实例进行内存管理由此创建)。不过,据我了解,这也符合规范。【参考方案4】:

您会考虑使用 Boost.MPL 吗?与 STL 不同,它允许创建包含类型而不是实例的容器。拥有从字符串到类型的映射将为您提供所需的工厂,不是吗?

【讨论】:

以上是关于按字符串名称的 C++ 通用对象工厂的主要内容,如果未能解决你的问题,请参考以下文章

模块加载时如何执行一次?

C++ 工厂模式 类模板实现

C++ 工厂模式 类模板实现

JavaScript对象:按名称作为字符串访问变量属性[重复]

按名称获取对象作为不带 eval 的字符串

不使用eval,按字符串名称访问命名空间的javascript对象