类的注册与动态生成

来源:互联网 发布:originpro8数据平滑 编辑:程序博客网 时间:2024/05/23 01:15

类的注册与动态生成

在开发中经常会遇到这样的问题,有一个基类及其派生的一些子类,需要根据子类的名字字符串创建相应的类对象。
比如有一些水果类,根据字符串”Apple”可以创建一个苹果类,根据字符串”Banana”创建一个香蕉类。

本文章中处理一个字符串处理的例子,问题描述如下
BAT牌匾公司专门经营牌匾制作业务,业务的主要流程如下,客户提供一个字符串和一些制作需求,公司根据客户的需求生成牌匾。
可能的制作需求如下:
- Capitalize: 将字符串首字母大写;
- AppendDollar: 在字符串尾部添加“$“号;
- DashString: 将字符串中的所有字母使用波折号连起来
- Bracket: 将整个字符串用括号括起来

在处理业务时,只需将制作需求按照顺序排列生成一条需求指令,业务机会根据需求指令将输入的字符串生成牌匾。
一条需求指令的例子如下:
Capitalize,DashString,AppendDollar

朴素的实现方法

首先使用朴素的方法实现

// plain_way.cpp#include <iostream>#include <map>#include <string>#include <vector>using namespace std;class PlaqueProcessor{public:     PlaqueProcessor(const string& n) : name_(n)    { }    virtual PlaqueProcessor()    { }    virtual string Process(const string& text) = 0;private:    string name_;};class Capitalize : public PlaqueProcessor{public:    Capitalize() : PlaqueProcessor("capitalize")    {         cout << "Create Capitalize Processor" << endl;    }    string Process(const string& text)    {        if (islower(text[0]))         {            return string(1, char(toupper(text[0]))) + text.substr(1);        }        return text;    }};class AppendDollar : public PlaqueProcessor{public:    AppendDollar() : PlaqueProcessor("append_dollar")    {        cout << "Create AppendDollar Processor" << endl;    }    string Process(const string& text)    {        return text + "$";    }};class DashString : public PlaqueProcessor{public:    DashString() : PlaqueProcessor("dash_string")    {        cout << "Create DashString Processor" << endl;    }    string Process(const string& text)     {        string dash_text = "";        for (int i=0; i < text.length(); ++i)         {            dash_text += string("-") + text[i];        }        return dash_text.substr(1);    }};typedef map<string, PlaqueProcessor*> NameProcessorMap;const NameProcessorMap& CreateProcessorManager() // mark01{    static NameProcessorMap processor_manager;    processor_manager["Capitalize"] = new Capitalize();    processor_manager["AppendDollar"] = new AppendDollar();    processor_manager["DashString"] = new DashString();    return processor_manager;}int main(int argc, char* argv){    const NameProcessorMap& processor_manager = CreateProcessorManager();    string text = "helloworld";    const int processor_count = 3;    string processor_names[] = {"Capitalize", "DashString", "AppendDollar"};    for (int i=0; i < processor_count; ++i)    {        string processor_name = processor_names[i];        NameProcessorMap::const_iterator citer = processor_manager.find(processor_name);        if (citer == processor_manager.end())            continue;        PlaqueProcessor* processor = citer->second;        text = processor->Process(text);    }    cout << "Plaque Sketch:" << text << endl; // 输出Plaque Sketch:H-e-l-l-o-w-o-r-l-d$    return 0;}

该方法的手动管理一个映射表,记录string到object之间的映射

这种方式的主要问题如下
1. CreateProcessorManager需要生成所有的processor对象,即使最后没有用到;例如上面的例子中,如果processor_count=2,那么AppendDollar其实是没用的,但是依然生成了该功能的对象,如果该对象初始化时需要加载很多资源的话,会造成不必要的浪费。
2. 每添加一个新类,都需要修改CreateFruitManager函数,将字符串和新类映射起来

虽然有上面的方法有点问题,但是这样的处理方式已经比较实用,后面的改进方法解决了上面的问题,但是基本思路还是一样的。

PS:在实际的环境中,每个功能都应该在一个单独的.h.cpp中,在这里只是为了说明问题,所有的功能都放在了同一个文件中.

注册方法

注册方法的接口是用宏的方式实现的,背后的实现方式和上面的方法差不多,也用到了映射表,但是使用了template和trait方法避免了上述的问题。

先简单介绍两个宏的用法,具体可再参考http://blog.csdn.net/blackbillow/article/details/3850587

// #variable表示将这是名的转换成字符串// a##b表示将a,b两个字符串拼接成一个新的字符串#define STR(s)     #s#define CONS(a,b)  int(a##e##b)printf(STR(vck));           // 输出字符串"vck"printf("%d/n", CONS(2,3));  // 2e3 输出:2000

首先给出使用注册方法后的实现方式,功能类本身没有进行任何改变

// registry_way.cpp#include "class_registry.h"#include <iostream>using namespace std;class PlaqueProcessor{    // ...};CLASS_REGISTRY_DEFINE(plaque_processor_registry, PlaqueProcessor);  // STEP1#define REGISTER_PROCESSOR(processor_name) \    CLASS_REGISTRY_REGISTER_CLASS( \        plaque_processor_registry, PlaqueProcessor, #processor_name, processor_name)#define CREATE_PROCESSOR(processor_name_string) \    CLASS_REGISTRY_CREATE_OBJECT(plaque_processor_registry, processor_name_string)class Capitalize : public PlaqueProcessor{    // ...};REGISTER_PROCESSOR(Capitalize);  // STEP2class AppendDollar : public PlaqueProcessor{    // ...};REGISTER_PROCESSOR(AppendDollar);class DashString : public PlaqueProcessor{    // ...};REGISTER_PROCESSOR(DashString);int main(int argc, char* argv[]){    string text = "helloworld";    const int processor_count = 3;    string processor_names[] = {"Capitalize", "DashString", "AppendDollar"};    for (int i=0; i < processor_count; ++i)     {        string processor_name = processor_names[i];        PlaqueProcessor* processor = CREATE_PROCESSOR(processor_name);  // STEP3        text = processor->Process(text);    }    cout << "Plaque Sketch:" << text << endl;    return 0;}

STEP1. 在定义基类PlaqueProcessor后,使用CLASS_REGISTRY_DEFINE定义一个注册表plaque_processor_registry,后面所有的子类都注册到该注册表中;
STEP2. 每当定义一个子类,调用REGISTER_PROCESSOR,将子类注册到上面的注册表中
STEP3. 使用时,调用CREATE_PROCESSOR,从注册表中取出name对应的类对象
其中,CLASS_REGISTRY_DEFINE使用plaque_processor_registry定义了一个PlaqueProcessor对注册表,REGISTER_PROCESSOR和CREATE_PROCESSOR两个宏根据plaque_processor_registry对原始注册宏函数的进一步包装

注册功能的实现方法

下面的注册实现方式是通用的,和功能类无关。

首先说明一下代码中的一个命名含义,方便理解
registry:名词,注册表
register:动词,注册
Base表示基类

// class_registry.h#ifndef CLASS_REGISTRY_H_#define CLASS_REGISTRY_H_#include <cstdlib>#include <cassert>#include <map>#include <vector>#include <string>#include <iostream>// 之所以分成两步实现MACRO_CONS,是因为A,B本身可能也是宏,// 例如此代码中用到的__LINE__,如果用一步实现,__LINE__将不会执行#define MACRO_CONS(A, B) MACRO_DO_CONS(A, B)#define MACRO_DO_CONS(A, B) A##Bnamespace util{// ClassRegistry的基类// ClassRegistry是模板类,将功能实现放在基类中,可以防止代码膨胀class ClassRegistryBase{protected:    typedef void* (*FuncPtr)();protected:    ClassRegistryBase() {}    ~ClassRegistryBase() {}    void DoRegisterClass(const std::string& entry_name, FuncPtr fptr);    void* DoCreateObject(const std::string& entry_name) const;public:    size_t RegisterCount() const;    const std::string& ClassName(size_t i) const;private:    typedef std::map<std::string, FuncPtr> ClassEntryMap; // 子类名到构造函数的映射    ClassEntryMap class_entry_map_;    std::vector<std::string> entry_names_;};// ClassRegistry<BaseClass>是BaseClass的注册表// 它提供了BaseClass子类名到构造函数的映射template <typename BaseClass>class ClassRegistry : public ClassRegistryBase{public:    typedef BaseClass* (*CtorPtr)();public:    ClassRegistry() {}    ~ClassRegistry() {}    void RegisterClass(const std::string& entry_name, CtorPtr ctor)    {        DoRegisterClass(entry_name, reinterpret_cast<FuncPtr>(ctor));    }    BaseClass* CreateObject(const std::string entry_name) const    {        return static_cast<BaseClass*>(DoCreateObject(entry_name));    }};// 一个BaseClass可以有多个注册表,为方便管理每个注册表需要一个// 注册表标签(名称),注册表标签记录了注册表及其对应的BaseClass//// RegistryTagBase<BaseClass>就是BaseClass的注册表标签的基类,它// 的每个子类都是一个注册表标签;如果有多个子类,那么就可以有多个// 注册表标签,每个标签都对应一个注册表。//// 使用时,宏CLASS_REGISTRY_DEFINE根据registry_name定义(拼接)// 一个RegistryTag类;后面对该RegistryTag的所有访问都可以通过// registry_name实现。//// 因此,一个BaseClass可以有多个注册表,可以用不同的// registry_name区分template <typename BaseClassName>struct RegistryTagBase {    typedef BaseClassName BaseClass;    typedef ClassRegistry<BaseClassName> RegistryType;};// RegistryTag是一个注册表标签,函数ClassRegistryInstance根据// 给定的RegistyTag创建及访问对应的注册表实例// 下面的方法使用了单例模式(effective cpp 条款47)// static对象会在首次函数调用的时候被初始化template <typename RegistryTag>typename RegistryTag::RegistryType& ClassRegistryInstance(){    static typename RegistryTag::RegistryType registry;    return registry;};// 注册一个类到指定的注册表中template <typename RegistryTag>class ClassRegister{    typedef typename RegistryTag::BaseClass BaseClass;public:    ClassRegister(const std::string& entry_name,        typename ClassRegistry<BaseClass>::CtorPtr ctor)    {        ClassRegistryInstance<RegistryTag>().RegisterClass(entry_name, ctor);    }    ~ClassRegister() {}};// 创建一个SubClass对象,并返回BaseClass指针template <typename BaseClass, typename SubClass>BaseClass* ClassRegistry_NewObject(){    return new SubClass();}} // namespace util// 为一个base_class定义一个注册类//// 第一个参数registry_name必须是全局惟一的// 一个base_class可以有多个注册类,使用registry_name定义//// 这个宏应该和base_class在同一个命名空间中#define CLASS_REGISTRY_DEFINE(registry_name, base_class) \    struct registry_name##RegistryTag : \        public ::util::RegistryTagBase<base_class> {};#define CLASS_REGISTRY_REGISTER_CLASS(registry_name, base_class, \                                      entry_name, class_name) \    static ::util::ClassRegister<registry_name##RegistryTag> \        MACRO_CONS(g_register_##class_name, __LINE__) ( \            entry_name, \            &::util::ClassRegistry_NewObject<base_class, class_name>)#define CLASS_REGISTRY_CREATE_OBJECT(registry_name, entry_name) \    ::util::ClassRegistryInstance<registry_name##RegistryTag>().CreateObject(entry_name)#define CLASS_REGISTRY_CLASS_COUNT(registry_name) \    ::util::ClassRegistryInstance<registry_name##RegistryTag>().RegisterCount()#define CLASS_REGISTRY_CLASS_NAME(registry_name, i) \    ::util::ClassRegistryInstance<registry_name##RegistryTag>().ClassName(i)#endif

class_registry层次

代码中主要有两个类
- ClassRegistry是注册表类,它为BaseClass生成一个注册表,可以将BaseClass的子类注册到该注册表中;
- RegistryTag可以认为是注册表名,它管理一个注册表实例
- registry_name是一个字符串,指向一个注册表名
对用户来讲,registry_name是其要关心的惟一的东西,RegistryTag和ClassRegistry对用户是透明的。

一个BaseClass可以有多个注册表,如上图,ABaseClass就有两个注册表实例,用户分别给它们起名叫A1、A2,其对应的RegistryTag为A1_RegistryTag、A2_RegistryTag(其实registry_name到RegistryTag的映射就是通过这样的拼接完成的)。

因为涉及了很多宏处理,可以使用下面的方式查看一下宏处理后的结果,方便理解
g++ -E registry_way.cpp -o registry_way.i
我们看下第二节中的三个步骤的宏处理结果

// STEP1CLASS_REGISTRY_DEFINE(plaque_processor_registry, PlaqueProcessor);  // =>struct plaque_processor_registryRegistryTag : public ::util::RegistryTagBase<PlaqueProcessor> {};;// STEP2REGISTER_PROCESSOR(Capitalize);  // =>static ::util::ClassRegister<plaque_processor_registryRegistryTag> g_register_Capitalize48 ( "Capitalize", &::util::ClassRegistry_NewObject<PlaqueProcessor, Capitalize>);// STEP3PlaqueProcessor* processor = CREATE_PROCESSOR(processor_name);  // =>PlaqueProcessor* processor = ::util::ClassRegistryInstance<plaque_processor_registryRegistryTag>().CreateObject(processor_name);

STEP1. 定义了一个注册表plaque_processor_registryRegistryTag
STEP2. 将子类名和其构造函数关联起来
STEP3. 根据子类名查找注册表

最后,给出注册表的实现

// class_registry.cpp#include "class_registry.h"namespace util {void ClassRegistryBase::DoRegisterClass(const std::string& entry_name,                                         FuncPtr fptr){    ClassEntryMap::const_iterator citer = class_entry_map_.find(entry_name);    if (citer != class_entry_map_.end())    {        std::cerr << "ClassRegiser: class " << entry_name            << " already registered. " << std::endl;        std::abort();    }    class_entry_map_[entry_name] = fptr;    entry_names_.push_back(entry_name);}void* ClassRegistryBase::DoCreateObject(const std::string& entry_name) const{    ClassEntryMap::const_iterator citer = class_entry_map_.find(entry_name);    if (citer == class_entry_map_.end())        return NULL;    return (*(citer->second))();}size_t ClassRegistryBase::RegisterCount() const{    return entry_names_.size();}const std::string& ClassRegistryBase::ClassName(size_t i) const{    assert(i < entry_names_.size());    return entry_names_[i];}}

最后的最后,这种注册方式存在的问题
- 由于ClassRegistry类中构造函数指针CtorPtr的类型是无参数的,所以注册的子类也要是无参数的,这是一个限制
- 每张注册表中的key和value是一一对应的,就如注册表一样,这种注册方式通过key得到的对象也是同一个,所以你不能通过”Capitalize”得到两个Capitalize对象;在本文的例子中,这是没问题的,但是如果你要买一打Apple,那么用这种方式你只会得到一个苹果对象。

本文中的代码见 https://github.com/jaysoon/cpp/tree/master/class_registry

0 0