#ifndef UNISE_SINGLETON_H_
#define UNISE_SINGLETON_H_
#include
#include
namespace unise
{
/// @brief Thread-safe, no-manual destroy Singleton template
template
class Singleton : boost::noncopyable
{
public:
/// @brief Get the singleton instance
static T* get() {
pthread_once(&_p_once, &Singleton::_new);
return _instance;
}
private:
Singleton();
~Singleton();
/// @brief Construct the singleton instance
static void _new() {
_instance = new T();
}
/// @brief Destruct the singleton instance
/// @note Only work with gcc
__attribute__((destructor)) static void _delete() {
typedef char T_must_be_complete[sizeof(T) == 0 ? -1 : 1];
(void) sizeof(T_must_be_complete);
delete _instance;
}
static pthread_once_t _p_once; ///< Initialization once control
static T* _instance; ///< The singleton instance
};
template
pthread_once_t Singleton::_p_once = PTHREAD_ONCE_INIT;
template
T* Singleton::_instance = NULL;
}
#endif
// factory.h
// Usage:
// class BaseClass { // base class
// ...
// };
// REGISTER_FACTORY(BaseClass);
// #define REGISTER_BASECLASS(name) REGISTER_CLASS(BaseClass, name)
//
// class Sub1 : public BaseClass {
// ...
// };
// REGISTER_BASE(Sub1);
// class Sub2 : public BaseClass {
// ...
// };
// REGISTER_BASE(Sub2);
//
// Note that REGISTER_BASE(sub1) should be put in cc file instead of h file,
// to avoid multi-declaration error when compile.
//
// Then you could get a new object of the sub class by:
// Base *obj = BaseClassFactory::get_instance("Sub1");
// or get a get_singleton object of the sub class by:
// Base* obj = BaseClassFactory::get_singleton("Sub1");
//
// This is convenient when you need decide the class at runtime or by flag:
// string name = "Sub1";
// if (...)
// name = "Sub2";
// Base *obj = BaseClassFactory::get_instance(name);
//
// If there should be only one instance in the program by desgin,
// get_uniq_instance could be used:
// Base *obj = BaseClassFactory::get_uniq_instance();
#ifndef UNISE_FACTORY_H_
#define UNISE_FACTORY_H_
#include
// factory.cc
#include "unise/factory.h"
namespace unise
{
BaseClassMap& g_factory_map()
{
static BaseClassMap factory_map;
return factory_map;
}
} // namespace unise
在很多程序设计中,经常会遇到这样的需求,即可以通过类的名字得到对应类型的对象,尤其是一种数据需要很多策略处理的时候。比如对于网页类型的识别,一篇网页可能是视频类型、新闻类型、图片类型、网站首页、百科等很多类型中的一种,网页类型对于搜索引擎来说是非常重要的,计算rank的时候网页类型往往是一个非常重要的因子。具体实现的时候,网页类型识别的策略可以封装在类中,这样一个策略就可以设计成一个类。但是后期随着对网页理解的越来越深入,就会出现以下两种情景:
1
|
PageTypeDetector* DetectorFactoryCreate(
const
string& class_name);
|
生成新闻网页类型识别的类可以如下调用:
1
|
PageTypeDetector* news_page_detector = DetectorFactoryCreate(
"NewsPageTypeDetector"
);
|
DetectorFactoryCreate工厂方法中的实现逻辑大致是这样:
1
2
3
4
5
|
if
(class_name ==
"NewsDocTypeDetector"
) {
return
new
NewsDocTypeDetector;
}
else
if
(class_name ==
"..."
) {
return
new
...;
}
|
使用如上工厂方法创建类的方式具有非常明显的缺陷,每添加或删除一个新类,都需要修改工厂方法内的程序(添加if判断或者删除if判断,并且需要添加新类的头文件或者类声明),当然了,因为程序有了修改所以就需要重新编译(如果很多其他模块依赖该程序的话,重新编译也是一笔不小的开销)。显然,这种方式虽然简单,但是极不易于维护。
1
|
Any GetInstanceByName(
const
string& class_name);
|
返回值为Any,因为不知道返回值究竟是什么类型,所以假定可以返回任何类型,这里的Any使用的是Boost中的Any。该方法中需要new一个类型为class_name的对象返回,那么应该如何new该对象呢?借用上面使用工厂方法的经验,可以进一步使用工厂类,对于每个类,都有一个相应的工厂类ObjectFactoryClassName,由该工厂类负责生成相应的对象(为什么要使用工厂类?后面再作简单介绍)。
有了工厂类,也需要将类名与工厂类对应起来,对应方式可以使用map
负责将新类对应的工厂类添加到全局变量object_factory_map的函数必须在使用object_factory_map之前执行。gcc中有一个关键字__attribute__((constructor)) ,使用该关键字声明的函数就可以在main函数之前执行。到现在,程序的结构类似这样:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
|
// 负责实现反射的文件reflector.h:
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();
// 调用文件test.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
return
0;
}
|
注:##是连接符,将两个字符串连接起来,#是将name作为字符串处理。
到这里还有一个问题,全局变量ObjectFactoryMap是不能放在头文件中的,因为如果多个类包含该头文件时,就会出现重复定义的错误,是编译不过的。因此,将该变量放在其源码reflector.cc文件中:
1
2
3
4
5
6
7
8
9
10
11
12
|
// reflector.h,包含声明:
extern
map
Any GetInstanceByName(
const
string& name);
// reflector.cc:
map
Any GetInstanceByName(
const
string& name) {
if
(object_factory_map.find(name) != object_factory_map.end()) {
return
object_factory_map[name]->NewInstance();
}
return
NULL;
}
|
上述程序编译能够通过,但是运行时出错,后来定位到是在使用全局变量object_factory_map时出错,经过调试了很久,在网上查相应的资料也没找到。经过不停的尝试,才发现原来是全局变量object_factory_map没有初始化,在仔细的测试了以后发现,是__attribute__((constructor))与全局变量类构造函数的执行顺序的问题,一般全局变量是在__attribute__(constructor)前完成初始化的,但是如果__attribute__是在main函数所在的文件,而全局变量是在其他文件定义的,那么__attribute__(constructor)就会在全局变量类构造函数前面执行,这样,上面的程序在全局变量类还没有完成初始化,也就是还没有执行构造函数,就在__attribute__(constructor)声明的函数中进行了使用,因此会出现问题。不过,在执行__attribute__时已经看到了全局变量的定义,只是没有执行全局变量的构造函数(这里,如果全局变量不是类,而是普通类型,是没有问题的)。所以,程序的结构还需要进一步修改。
1
2
3
4
5
|
// reflector.cc
map
static
map
new
map
return
*factory_map;
}
|
这样定义还有另外一个优点,程序只是在真正需要调用g_objectfactory_map时才会生成相应的对象,而如果程序没有调用,也不会生成对应的对象。当然,在这里new一个对象的代价不大,但是如果new的对象非常耗时的话,这种使用函数中static变量代替全局变量方法的优势就非常明显了。到现在反射程序变成如下这样:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
|
// 负责实现反射的文件reflector.h:
// 工厂类的基类
class
ObjectFactory {
public
:
virtual
Any NewInstance() {
return
Any();
}
};
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
static
map
new
map
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;
}
|
到现在接近尾声了,不过在很多时候,我们都是在已有基类的基础上添加新的类,就好比上述网页识别的程序,各个识别策略类都继承共同的基类,这样,我们可以进一步修改反射程序,将GetInstanceByName放在另外一个类中,返回的是基类的指针,因此在定义基类时也需要注册一个宏,如下所示,同时需要修改objector_factory_map的结构为map
1
2
3
4
5
6
7
8
9
10
11
12
13
|
#define REFLECTOR_BASE(base_class) \
class
base_class##Reflector { \
public
: \
static
base_class* GetInstanceByName(
const
string& name) { \
map
map
if
(iter == map.end()) { \
return
NULL; \
} \
Any object = iter->second->NewInstance(); \
return
*(object.any_cast
} \
};
|
这里就不再详细讲修改后的代码了,有兴趣的朋友可以自己实现。
注:
1
2
3
4
|
#define REFLECT(name) \
Any GetInstanceByName(
const
string& class_name) {
return
Any(
new
name);
}
|
如果是多个类使用的话,那么就会出现多个函数的定义。如果也借助工厂类的实现,如下实现:
1
2
3
4
|
#define REFLECT(name) \
Any GetInstanceByName##name(
const
string& class_name) {
return
Any(
new
name);
}
|
这样是不会出现重复定义了,但是这样在生产新的对象时需要指定特定的函数,这不又回到原点了吗?因此工厂类充当的是个中介的角色,我们可以保存工厂类,然后根据名称寻找特定的工厂类来生成对应的对象。
注:
为什么需要使用函数添加工厂类?因为在程序中,全局空间中只能是变量的声明和定义,而不能是语句,例如:
需要注意的知识点: