?!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
在很多程序设计中Q经怼遇到q样的需求,卛_以通过cȝ名字得到对应cd的对象,其是一U数据需要很多策略处理的时候。比如对于网늱型的识别Q一网可能是视频cd、新ȝ型、图片类型、网站首c百U等很多cd中的一U,|页cd对于搜烦引擎来说是非帔R要的Q计rank的时候网늱型往往是一个非帔R要的因子。具体实现的时候,|页cd识别的策略可以封装在cMQ这样一个策略就可以设计成一个类。但是后期随着对网느解的来深入,׃出现以下两种情景Q?/strong>
需要添加新的网늱型,因此需要添加对应的cd识别c;
有些cd已经不再需要或者是q行了重新划分,那么需要删除掉q些cd或者是让这些类型识别模块不再生效?br style="margin: 0px; padding: 0px;"/>
׃软g开发公?/a>q种应用场景下,d或移除网늱型识别模块时Q最好能够非常方便,q且不会影响到已有的E序?/p>
一个比较好的方案是Q定义一个类型识别的基类PageTypeDetectorQ每个类型识别策略都l承自这个基cR比如需要一个新闻页识别的新{略Q那么定义类NewsPageTypeDetectorQ该cȝ承PageTypeDetector。在dNewsPageTypeDetector到网늱型识别的ȝ序时Q在配置文g中进行配|,dNewsPageTypeDetectorc,让该cȝ效,而主E序和其他类型识别策略的E序都不需要进行改动。另外,如果不再需要图片网늱型识别,那么把囄cd识别对应的类名直接从配置发文件中删除卛_?br style="margin: 0px; padding: 0px;"/>
Z实现上述目标Q我们需要从cd到类型的映射Q可以称为反。因为配|文件中的信息在E序内部得到的都是纯字符ԌE序需要根据字W串生成对应的识别类。当Ӟq个在本w已包含反射机制的程序设计语a中很Ҏ实现Q比如JAVAQ但是由于C++中语a本n不支持这U机Ӟ因此Q需要用其他的方法来模拟q种机制?br style="margin: 0px; padding: 0px;"/>
首先Q我们从最单的方式开始,定义一个工厂方法,该方法负责根据类名生成相应类的对象,函数定义可以如下
PageTypeDetector* DetectorFactoryCreate(const string& class_name);
生成新闻|页cd识别的类可以如下调用Q?/p>
PageTypeDetector* news_page_detector = DetectorFactoryCreate("NewsPageTypeDetector");
DetectorFactoryCreate工厂Ҏ中的实现逻辑大致是这P
if (class_name == "NewsDocTypeDetector") {
return new NewsDocTypeDetector;
} else if (class_name == "...") {
return new ...;
}
使用如上工厂Ҏ创徏cȝ方式h非常明显的缺P每添加或删除一个新c,都需要修改工厂方法内的程序(dif判断或者删除if判断Qƈ且需要添加新cȝ头文件或者类声明Q,当然了,因ؓE序有了修改所以就需要重新编译(如果很多其他模块依赖该程序的话,重新~译也是一W不的开销Q。显Ӟq种方式虽然单,但是极不易于l护?/p>
q里Q提Z个用非常方便ƈ且易于维护的解决ҎQ那是使用宏。虽然c++创始人Bjarne Stroustrup极力反对使用宏,但是在一些特定的场景中合理的使用宏会带来意想不到的效果?/p>
׃软g开发公?/a>首先Q从使用宏最单的一个实现开始,目标是可以通过cȝ名字得到相应的对象,因此应该有个ҎcM于如下:
1Any GetInstanceByName(const string& class_name)Q?br style="margin: 0px; padding: 0px;"/>
q回gؓAnyQ因Z知道q回值究竟是什么类型,所以假定可以返回Q何类型,q里的Any使用的是Boost中的Any。该Ҏ中需要new一个类型ؓclass_name的对象返回,那么应该如何new该对象呢?借用上面使用工厂Ҏ的经验,可以q一步用工厂类Q对于每个类Q都有一个相应的工厂cObjectFactoryClassNameQ由该工厂类负责生成相应的对象(Z么要使用工厂c?后面再作单介l)?/p>
有了工厂c,也需要将cd与工厂类对应hQ对应方式可以用map<string, ObjectFactory*> object_factory_mapQobject_factory_map负责从类名到相应工厂cȝ映射Q这P可以通过cȝ名字扑ֈ对应ObjectFactoryQ然后用ObjectFactory生成相应的对象。但是如何将相应的工厂类d到object_factory_map中去呢,我们需要在定义新类的时候就对应的工厂cL加到object_factory_map中,q里需要一个函数负责添加工厂类到object_factory_map中去Qؓ什么需要一个函数负责?最后作单说明)?/p>
负责新cd应的工厂cL加到全局变量object_factory_map的函数必d使用object_factory_map之前执行。gcc中有一个关键字__attribute__((constructor)) Q用该关键字声明的函数可以在main函数之前执行。到现在Q程序的l构cMq样Q?/strong>
// 负责实现反射的文件reflector.h:
map<string, ObjectFactory*> object_factory_map;
Any GetInstanceByName(const string& name) {
if (object_factory_map.find(name) != object_factory_map.end()) {
return object_factory_map[name]->NewInstance();
}
return NULL;
}
#define REFLECTOR(name) \
class ObjectFactory##name { \
public: \
Any NewInstance() { \
return Any(new name); \
} \
}; \
void register_factory_##name() { \
if (object_factory_map.find(#name) == object_factory_map.end()) { \
object_factory_map[#name] = new ObjectFactory##name(); \
} \
} \
__attribute__(constructor)void register_factory##name();
// 调用文gtest.cc
class TestClass {
public:
void Out() {
cout << "i am TestClass" << endl;
}
};
REFLECTOR(TestClass);
// main函数
int main() {
Any instance = GetInstanceByName("TestClass");
TestClass* test_class = instance.any_cast<TestClass>();
return 0;
}
到这里还有一个问题,全局变量ObjectFactoryMap是不能放在头文g中的Q因为如果多个类包含该头文gӞ׃出现重复定义的错误,是编译不q的。因此,该变量攑֜其源码reflector.cc文g中:
// reflector.hQ包含声明:
extern map<string, ObjectFactory*> object_factory_map;
Any GetInstanceByName(const string& name)Q?/p>
// reflector.ccQ?/p>
map<string, ObjectFactory*> object_factory_map;
Any GetInstanceByName(const string& name) {
if (object_factory_map.find(name) != object_factory_map.end()) {
return object_factory_map[name]->NewInstance();
}
return NULL;
}
上述E序~译能够通过Q但是运行时出错Q后来定位到是在使用全局变量object_factory_map时出错,l过调试了很久,在网上查相应的资料也没找到。经q不停的试Q才发现原来是全局变量object_factory_map没有初始化,在仔l的试了以后发玎ͼ是__attribute__((constructor))与全局变量cL造函数的执行序的问题,一般全局变量是在__attribute__(constructor)前完成初始化的,但是如果__attribute__是在main函数所在的文gQ而全局变量是在其他文g定义的,那么__attribute__(constructor)׃在全局变量cL造函数前面执行,q样Q上面的E序在全局变量c还没有完成初始化,也就是还没有执行构造函敎ͼ在__attribute__(constructor)声明的函Cq行了用,因此会出现问题。不q,在执行__attribute__时已l看C全局变量的定义,只是没有执行全局变量的构造函敎ͼq里Q如果全局变量不是c,而是普通类型,是没有问题的Q。所以,E序的结构还需要进一步修攏V?/p>
׃软g开发公?/a>现在解决如何定义和用全局变量object_factory_map的问题。既然我们不能直接用该变量Q那么可以通过昄调用函数来返回该变量Q如果直接在函数中new一个对象返回的话,那么每次调用都会new一个新的对象,而我们全局只需要一个该对象Q这时该是static出现的时候了。我们可以这样定义:
// reflector.cc
map<string, ObjectFactory*>& object_factory_map() {
static map<string, ObjectFactory*>* factory_map = new map<string, ObjectFactory*>;
return *factory_map;
}
q样定义q有另外一个优点,E序只是在真正需要调用g_objectfactory_map时才会生成相应的对象Q而如果程序没有调用,也不会生成对应的对象。当Ӟ在这里new一个对象的代h不大Q但是如果new的对象非常耗时的话Q这U用函Cstatic变量代替全局变量Ҏ的优势就非常明显了。到现在反射E序变成如下q样Q?/p>
// 负责实现反射的文件reflector.h:
// 工厂cȝ基类
class ObjectFactory {
public:
virtual Any NewInstance() {
return Any();
}
};
map<string, ObjectFactory*>& object_factory_map();
Any GetInstanceByName(const string& name);
#define REFLECTOR(name) \
class ObjectFactory##name : public ObjectFactory { \
public: \
Any NewInstance() { \
return Any(new name); \
} \
}; \
void register_factory_##name() { \
if (object_factory_map().find(#name) == object_factory_map().end()) { \
object_factory_map()[#name] = new ObjectFactory##name(); \
} \
} \
__attribute__(constructor)void register_factory##name()
// reflector.cc
map<string, ObjectFactory*>& object_factory_map() {
static map<string, ObjectFactory*>* factory_map = new map<string, ObjectFactory*>;
return *factory_map;
}
Any GetInstanceByName(const string& name) {
if (object_factory_map().find(name) != object_factory_map().end()) {
return object_factory_map()[name]->NewInstance();
}
return NULL;
}
到现在接q尾CQ不q在很多时候,我们都是在已有基cȝ基础上添加新的类Q就好比上述|页识别的程序,各个识别{略c都l承共同的基c,q样Q我们可以进一步修改反程序,GetInstanceByName攑֜另外一个类中,q回的是基类的指针,因此在定义基cL也需要注册一个宏Q如下所C,同时需要修改objector_factory_map的结构ؓmap<string, map<string, ObjectFactory> >Q第一个key是基cȝ名字Q第二map中的key是生成类的名字,基类宏的定义cM如下Q?/p>
#define REFLECTOR_BASE(base_class) \
class base_class##Reflector { \
public: \
static base_class* GetInstanceByName(const string& name) { \
map<string, ObjectFactory*>& map = object_factory_map()[#base_class]; \
map<string, ObjectFactory*>::iterator iter = map.find(name); \
if (iter == map.end()) { \
return NULL; \
} \
Any object = iter->second->NewInstance(); \
return *(object.any_cast<base_class*>()); \
} \
};
q里׃再详l讲修改后的代码了,有兴的朋友可以自己实现?/p>
注:
至于上面Z么需要用工厂类Q而不是直接new一个对应的对象q回Q原因是直接new是不可以的。例如如下定?nbsp;
#define REFLECT(name) \
Any GetInstanceByName(const string& class_name) {
return Any(new name);
}
如果是多个类使用的话Q那么就会出现多个函数的定义。如果也借助工厂cȝ实现Q如下实玎ͼ
#define REFLECT(name) \
Any GetInstanceByName##name(const string& class_name) {
return Any(new name);
}
q样是不会出现重复定义了Q但是这样在生新的对象旉要指定特定的函数Q这不又回到原点了吗Q因此工厂类充当的是个中介的角色Q我们可以保存工厂类Q然后根据名U寻扄定的工厂cL生成对应的对象?/p>
注:
Z么需要用函数添加工厂类Q因为在E序中,全局I间中只能是变量的声明和定义Q而不能是语句Q例如:
可以q样写:
int a = 10;
int main() {}
但是不能q样写:
int a;
a = 10;
int main() {}
需要注意的知识点:
工厂模式Q?/strong>
全局变量的定义需要注意,不能定义在头文g中(当如Q如果经q特D处理,例如使用#ifndef保护另说Q;
Anycd的实玎ͼQ准备写另外一文章来探讨其实现细节)
宏的定义以及使用Q(基本覆盖了宏的所有知识)
全局变量构造函C__attribute__((constructor))的执行顺序;Q调试了很久Q?/strong>
__attribute__((constructor))的问题;Q编译器有关Q放在函数定义前或定义后Q?/strong>
全局I间只能是声明或者定义,不能是语句;
static在函C的用;
全局变量cȝ定义与用?/strong>