类的注册与动态生成
在开发中经常会遇到这样的问题,有一个基类及其派生的一些子类,需要根据子类的名字字符串创建相应的类对象。
比如有一些水果类,根据字符串”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); // STEP2
class 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##B
namespace 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
我们看下第二节中的三个步骤的宏处理结果
// STEP1
CLASS_REGISTRY_DEFINE(plaque_processor_registry, PlaqueProcessor); // =>
struct plaque_processor_registryRegistryTag : public ::util::RegistryTagBase<PlaqueProcessor> {};;
// STEP2
REGISTER_PROCESSOR(Capitalize); // =>
static ::util::ClassRegister<plaque_processor_registryRegistryTag> g_register_Capitalize48 ( "Capitalize", &::util::ClassRegistry_NewObject<PlaqueProcessor, Capitalize>);
// STEP3
PlaqueProcessor* 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
本文探讨了在C++中如何处理类的注册和动态生成问题。通过一个牌匾制作业务的示例,介绍了朴素的实现方法,即手动维护映射表,以及注册方法,利用宏和模板来解决手动管理的缺点。类注册的实现方法包括定义一个通用的注册表类和注册标签,允许用户通过字符串动态创建对象,但存在限制,如子类必须无参数构造,并且注册表中的对象是唯一的。

1030

被折叠的 条评论
为什么被折叠?



