类的注册与动态生成
来源:互联网 发布: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
代码中主要有两个类
- 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
- 类的注册与动态生成
- 监听器的动态注册与静态注册
- 监听的动态注册与静态注册的区别
- Oracle DBA之监听的静态注册与动态注册
- oracle监听的静态注册,与动态注册理解
- Oracle监听器的静态注册与动态注册
- BroadCastReceiver静态注册、与动态注册的区别?
- Android广播的静态与动态注册
- winform 动态生成panel 动态生成透明panel 带边框的panel 并注册事件
- 动态生成表单与调用动态生成的控件
- Oracle Listener 动态注册 与 静态注册
- Oracle Listener 动态注册 与 静态注册
- ORACLE 监听动态注册与静态注册
- Oracle Listener 动态注册 与 静态注册
- oracle监听动态注册与静态注册
- Oracle Listener 动态注册 与 静态注册
- Oracle Listener 动态注册 与 静态注册
- Oracle Listener 动态注册 与 静态注册
- centos6使用 swig3.0.6 编译c,golang 1.4.2调用
- 九度oj 1447
- 命令处理、getopt_long函数说明
- C#进程同步之管道通信
- 图的环路问题
- 类的注册与动态生成
- C++中cin和cin.getline函数连用的问题
- Java学习笔记---基本类型、运算优先级的一些知识整理
- Target-Action(View实现button的效果)
- 【HDU1087】【Super Jumping! Jumping! Jumping!】
- 结构体4
- Java 反射机制原理
- Linux 无法开机 显示 an error occurred during the file&nb
- const用法详解