并行Html解析作为一个研究性课题来说,十分具有挑战性。因为html特有的上下文关联特性,使得它很难被一般的自动机解析,即使是支持大部分编程语言的下推自动机,也很难描述。
本文,就从html的特点分析入手,解释html语法的特点,和将其并行化的一些思考。
并行Html解析作为一个研究性课题来说,十分具有挑战性。因为html特有的上下文关联特性,使得它很难被一般的自动机解析,即使是支持大部分编程语言的下推自动机,也很难描述。
本文,就从html的特点分析入手,解释html语法的特点,和将其并行化的一些思考。
今天,Canonical发布了Ubuntu 16.04 LTS ‘Xenial Xerus’,作为一个长期支持版,确实很让人期待。
Ubuntu 16.04提供了很多激动人心的新功能。
例如新的内核升级到Linux4.4,大量的软件版本更新,而且Ubuntu 16.04将会是最后一个支持32位架构的Ubuntu系统,日后将会是64位的时代。
LLVM函数的调用时声明插入
如果调用一个未声明的函数,我们知道肯定是不正确的,但符号表中,可能预先存有该函数的 FunctionType,这时即使未扫描到该函数,我们也可以用Module中的getOrInsertFunction方法,获取或插入一个函数。
Constant Module::getOrInsertFunction (
StringRef Name,
FunctionType T,
AttributeSet AttributeList
)
其行为是这样的:
是不是很方便呢?
这样应该可以减少一次函数的声明遍历。
本专栏文章列表
一、何为面向对象
二、C语言也能实现面向对象
三、C++中的不优雅特性
四、解决封装,避免接口
五、合理使用模板,避免代码冗余
六、C++也能反射
七、单例模式解决静态成员对象和全局对象的构造顺序难题
八、更为高级的预处理器PHP
九、Gtkmm的最佳实践
本系列文章由 西风逍遥游 原创,转载请注明出处:西风广场 http://sunxfancy.github.io/
今天要为大家介绍C语言的面向对象设计方法,正如题记上面所说,面向对象是一种思想,而并非是一种语言,我们将会介绍C语言实现的面向对象开发方式。
众所周知,C++中的面向对象十分方便,但在C中,并没有类,但我们可以通过C的结构体来实现,然后,手动将this指针传入
目前这个方法,应该是C语言设计中,简便易用的方式,而且能较好的体现面向对象的设计思路,然而遗憾的是,没有继承和多态。
例如,我们这样一个C++类
1 | class test { |
那么我们可以这样转换为一个C类
1 | /* test.h */ |
1 | /* test.c */ |
其实思路也很清晰,思路简单易懂,实现也很清新明快,在各类C工程中使用极为广泛。
如果你希望学习C语言的GUI程序设计,那么,必定要学习的就是GObject的类实现方式。
GObject相当于从C层面上模拟了一个C++的类对象模型,实现当然相对复杂的多。
下面我们来实际看一下一个GTK的窗口类,这是GTK+-3.0的一段样例:
1 | /* appwin.h */ |
而其真实的定义是在.c文件中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
struct _AppWindow
{
GtkApplicationWindow parent;
};
struct _AppWindowClass
{
GtkApplicationWindowClass parent_class;
};
typedef struct _AppWindowPrivate AppWindowPrivate;
struct _AppWindowPrivate
{
GSettings *settings;
GtkWidget *stack;
GtkWidget *search;
GtkWidget *searchbar;
GtkWidget *searchentry;
GtkWidget *gears;
GtkWidget *sidebar;
GtkWidget *words;
GtkWidget *lines;
GtkWidget *lines_label;
};
G_DEFINE_TYPE_WITH_PRIVATE(AppWindow, app_window, GTK_TYPE_APPLICATION_WINDOW);
/* 后面有具体的实现方法,这里就不一一列举 */
我们发现,这种定义方式比C++中的其实更有优势,封装的更加彻底。为何这样说呢?首先,我们的声明文件十分的简洁,如果公开方法不修改的话,那么将其余内容如何改动,都不会影响我们的外部接口。
其次,由于需要显示的向GObject注册,那么动态进行类注册就成为可能,这样的设计优势表现在哪里呢?多语言的互通性就很好了,因为很多动态语言,是支持类的动态加载以及反射加载的。
另外,vala语言就是基于GObject类型的,他是一门新兴的编译时语言,但其也有很多动态语言的特性,用其开发gtk程序,比C具有明显的优势。
本专栏文章列表
一、何为面向对象
二、C语言也能实现面向对象
三、C++中的不优雅特性
四、解决封装,避免接口
五、合理使用模板,避免代码冗余
六、C++也能反射
七、单例模式解决静态成员对象和全局对象的构造顺序难题
八、更为高级的预处理器PHP
九、Gtkmm的最佳实践
本系列文章由 西风逍遥游 原创,转载请注明出处:西风广场 http://sunxfancy.github.io/
今天来说一说C++中不优雅的一些问题,C++虽然是面向对象的设计语言,但也有很多缺陷和弊病,我们将会讨论如何通过良好的设计解决这些问题。
C++编译慢已经成为了业界共识,一个大型C++项目甚至要用专用的服务器编译好久才能完成,Java和.net作为大型开发平台, 却也没发现编译如此缓慢的问题,那么究竟是什么,导致了C++编译难的问题呢?
C++中模板有个很神奇的问题,就是实现和声明都必须被使用者引用,这段模板代码才有效,也就是说,模板是在编译时展开的代码生成机制。
我们不妨做个实验,这是类的声明:1
2
3
4
5
6
7
8
9
10template<class T>
class CObject
{
public:
CObject(T k) {obj = k;}
~CObject() {}
T getObj();
private:
T obj;
};
下面是类的实现:1
2
3
4
5
6#include "CObject.h"
template<class T>
T CObject<T>::getObj(){
return this->obj;
}
主函数中调用:1
2
3
4
5
6
7
8
9
10
11#include <cstdio>
#include "CObject.h"
using namespace std;
int main(){
CObject<int> Obj(10);
int k = Obj.getObj();
printf("%d\n", k);
return 0;
}
一切看起来是那么的顺利,但是!我的电脑给我显示如下错误信息:1
2
3
4
5
6
7
8
9
10Scanning dependencies of target template_test
[ 50%] Building CXX object CMakeFiles/template_test.dir/src/CObject.cpp.o
[100%] Building CXX object CMakeFiles/template_test.dir/src/main.cpp.o
Linking CXX executable template_test
CMakeFiles/template_test.dir/src/main.cpp.o:在函数‘main’中:
main.cpp:(.text+0x22):对‘CObject<int>::getObj()’未定义的引用
collect2: error: ld returned 1 exit status
make[2]: *** [template_test] 错误 1
make[1]: *** [CMakeFiles/template_test.dir/all] 错误 2
make: *** [all] 错误 2
链接器告诉我,我们找不到一个叫做‘CObject
如果你这样想就错了,上网查找解决方案,得到的回复居然是这样:#include "CObject.h"
=> #include "CObject.cpp"
omg,那我还不如把两个文件写成一个hpp来的方便呢,其实C++也是推荐你这样做的,理由就是——模板是编译时,在用到的时候进行代码展开得到的
如果不这样做,链接器是不会找到对应的代码的。
那么也找到了很多大型工程如boost库,为何编译缓慢的直接原因,大量的模板展开消耗了巨大的资源,而且模板展开是很不利于代码复用的,同样的算法,换一种类型,必须全部编译,生成新的代码,并且这类模板生成的代码,不能提前编译成二进制库,这样的结果就是,项目哪里改动一点,好多文件重复编译,造成编译十分缓慢。
C++的类并没有很好的将代码封起来,这和上次讲到的GObject对比可以发现,C++的私有变量是一同放置在类的声明中,而我们知道,一个类的声明,是会被很多其他类引用的。
那么,思考我们的C++编译过程,很多类都引用了一个.h文件,那么这个.h文件一旦发生更改,那么所有引用这个文件的cpp文件都将被触发重复编译,而我们在实现一个类时,对类的成员函数小修小补是很平常的,但由于封装的不彻底,那么我们的项目又将被反复编译,带来编译的速度缓慢。
而且,如果是库的话,那么私有成员的更新甚至还会影响用户使用,非常麻烦。
例如下面这段代码:1
2
3
4
5
6
7
8
9
10
11
12class Test {
public:
Test();
~Test();
void Show();
private:
std::string message;
int pointer;
void formatMessage(std::string&);
};
很明显,一般的C++类,私有成员都会比公开成员多,那么私有成员修改一点,哪怕只是一不小心多了个空格,都会带来这个文件的更新,触发makefile的重编译,带来了低效率。
最新的C++11,引入了众多的新特性,包括好用的auto关键字以及模板元编程特性等,但这些,还是不能弥补反射机制缺失带来的影响。反射是对象串行化、GUI界面事件响应和根据数据动态调用代码等技术的核心,缺乏反射机制,会使得C++很多地方十分的不便。
很多大型软件,如firefox,在实现中,往往搭建了反射框架,供系统使用。但由于C++本身语法的问题,缺乏反射依旧会使得类书写变得困难。
C++的跨平台性真的不好,甚至很多编译器上都会出现匪夷所思的问题,例如在不同平台上,基本类型的大小会随CPU字长而变化,如果有跨平台需求的软件,最好使用跨平台定义的类型。
C++的结构体中数据往往有内存对齐的问题,有些编译器还能通过编译器指令对其设置,这些问题最好还是能避开就避开。
跨平台时,还应小心异常处理的代码,因为有些版本的C++编译器对抛出的异常规格并不很遵守规范。
另外,不同平台的宽字符集也是大问题,往往并不能轻松统一,另外MinGW里貌似就没有宽字符- -
本专栏文章列表
一、何为面向对象
二、C语言也能实现面向对象
三、C++中的不优雅特性
四、解决封装,避免接口
五、合理使用模板,避免代码冗余
六、C++也能反射
七、单例模式解决静态成员对象和全局对象的构造顺序难题
八、更为高级的预处理器PHP
九、Gtkmm的最佳实践
本系列文章由 西风逍遥游 原创,转载请注明出处:西风广场 http://sunxfancy.github.io/
恩,今天我们来讨论,如何通过设计,解决C++中的不优雅特性,改进项目的结构,改善编译速度。
上次我们提到,如果一个类的封装不好,容易导致种种不便,那么如何设计能够避免这种现象呢?
1 | class test { |
最简单的想法就是将实现和声明分开,这也是C++提倡的,这样虽然文件会增多,但编译速度和代码的清晰度会提升。
1 | /* test.h */ |
1 | /* test.cpp */ |
很明显的,这样我们改动cpp文件中,.h文件不会受到影响,但假若我的private方法增加了,那么还是需要改动.h文件,进而会影响所有引用我的部分,为了避免这种情况出现,有什么好设计方法么?
一种标准的设计模式是使用接口,这在很多库的设计时也被经常采用,核心思想是通过多态调用的方式,避免内部方法的暴露。
接口一般就是C++的多态类:
1 | /* Itest.h */ |
1 | /* Itest.cpp */ |
让test从这个接口继承出来:1
2
3
4
5
6
7
8/* test.h */
class test : public Itest {
public:
virtual void print();
virtual void print2();
private:
int k;
};
这样的好处当然十分明显了,将类转成接口的形式,就能方便的修改下面的实现类,无论实现类如何改动,都在模块范围内,接口不变。
但这样做的坏处也很明显,如果C++大量使用这样的方式实现内部封装,那么很多情况下效率比较低,而且代码复杂度就上来了,需要添加很多的接口类。
下面介绍一种简单的方式来实现类封装性的提升,首先还是看这个test类,我们将其提示为test2:1
2
3
4
5
6
7
8/* test2.h */
class test2 {
public:
void print();
void print2();
private:
int k;
};
这里的k实际上并不需要写在这里,我们需要的是将private的部分整体的封装成一个类:
1 | /* test2.h */ |
1 | /* test2.cpp */ |
这时我们发现,这种封装可以很有效的解决类的接口不便的问题,而由于只使用了类指针,所以我们并不需要前向声明这个私有类,于是这个类可以方便的被修改,从而避免了接口和多态调用的问题。
这种设计还有一个用途,假若你有另外的代码生成器生成的代码,需要和已有的类嵌入使用,那么推荐使用这种方式,Qt中就是这样做的:
1 | #ifndef MAINWINDOW_H |
1 | #include "mainwindow.h" |
我们发现这里有一个神奇的代码1
2
3namespace Ui {
class MainWindow;
}
其实这只是另外一个类,和本类并不同名,Ui::MainWindow是qt设计器帮忙生成的类,用来标注UI界面生成的一些代码,为了让代码很好的和我们自己的类统一起来,他们用了这种方式。
本专栏文章列表
一、何为面向对象
二、C语言也能实现面向对象
三、C++中的不优雅特性
四、解决封装,避免接口
五、合理使用模板,避免代码冗余
六、C++也能反射
七、单例模式解决静态成员对象和全局对象的构造顺序难题
八、更为高级的预处理器PHP
九、Gtkmm的最佳实践
本系列文章由 西风逍遥游 原创,转载请注明出处:西风广场 http://sunxfancy.github.io/
下面我们来讨论一下,如何解决模板的不易封装的问题。
我们提供这样一种思路,对于链表一类的通用类型,我们尽量采取强制类型转换的方式,尽量避免模板的滥用。
同样,我们应该避免对结构体的直接存储,尽量使用类似java的指针传递方式来传递对象。
我们首先来写一个单类型的list
1 | #ifndef LIST_C_H |
这里我们使用了上面讲到的封装方式,降低了类间的耦合度
1 | #include "list_c.h" |
这是一个简单的链表,只是作为示例使用,写了插入和获取的两个方法。
而为了通用性支持,我们写一个模板,进行类型的强制转换:
1 | #ifndef LIST |
这样,带来的好处有,首先能够将模板封装操作,其次,能够在封装类中,动态的调整内部实例。
对于一个传入的类型,你可以判断一下,是否适合当前的模板,如果不适合,可以在其中动态的报错。
最后是模板的使用:1
2
3
4
5
6
7
8
9
10
11
12
13#include <iostream>
#include "list"
using namespace std;
int main(){
list<long> testlist;
testlist.insert(10);
testlist.insert(20);
long k = testlist.get(1);
printf("%d\n", k);
return 0;
}
本专栏文章列表
一、何为面向对象
二、C语言也能实现面向对象
三、C++中的不优雅特性
四、解决封装,避免接口
五、合理使用模板,避免代码冗余
六、C++也能反射
七、单例模式解决静态成员对象和全局对象的构造顺序难题
八、更为高级的预处理器PHP
九、Gtkmm的最佳实践
本系列文章由 西风逍遥游 原创,转载请注明出处:西风广场 http://sunxfancy.github.io/
今天我们来探讨C++的反射问题,缺乏反射机制一直是C++的大问题,很多系统的设计时,需要根据外部资源文件的定义,动态的调用内部的函数和接口,如果没有反射,将很难将外部的数据,转换为内部的方法。
Java和.net的反射机制很容易实现,由于其动态语言的特性,在编译时就存储了大量的元数据,而在动态装载时,也是根据这些元数据载入的模块。由于C++缺乏这些信息,往往并不能很好的动态装载和链接。操作系统为了实现C和C++的动态装载功能,特意设计了动态链接库,将符号表保存在动态库中,运行时重定位代码,然后进行链接操作。而这是操作系统实现的,并不能很好的被用在用户工程中,所以我们有必要自己构建一套元数据集合,保存反射所需的内容。
反射的核心就是根据字符串名字,创建对应的类或者调用对应类的方法,为此,我们使用C++中的map
1 | std::map<std::string, meta_class*> |
meta_class 是保存一个类中的关键元数据用的类,可以支持反射构造,反射调用函数等功能。
meta_func 是保存一个方法的关键信息类,但由于方法有不定的参数和返回类型,我们使用模板的方式,将一个抽象存储的成员函数指针,转换为我们确定类型的成员函数指针,然后再去调用,达到动态调用的目的:1
2
3
4
5
6template <typename T, typename R, typename... Args>
R Call(T* that, Args... args) {
R (T::*mp)();
mp = *((R (T::**)())func_pointer);
return (that->*mp)(args...);
}
这里的代码十分混乱,如果你没学过C的函数指针的话,建议先去补习一下函数指针的定义和用法。
这里涉及到的是成员函数指针的传递,一会儿将会详细讲解如何传递任意一个函数指针。
首先,我们肯定要为类对象建立meta_class的模型,但每个meta_class,应该都能够构建本类的对象,为了实现这一特点,我们想到了模板:
1 | template<typename T> |
为了让每个类都能有统一的创建方法,我们将使用IMetaClass接口进行多态调用
1 | class IMetaClass { |
这里我们在接口类中编写了方法和成员函数,我觉得这是C++的优势,不像Java,为了安全性,而取消了这么简单好用的功能。
接口统一实现相同的类对象构建方式,避免了在实现类中反复编写的困难。
这样,我们只要在每个类的定义时,向我们的类注册器注册该MetaClass对象就可以了
但问题是,如何才能在类定义时编写代码呢?我们的C和C++可是只能在函数中调用代码,不能像脚本一样随时随地执行。
我们发现,C++有一个很有趣的特性,有些代码是可以在main函数执行前就执行的:
1 | class test |
执行代码,哦?好像不大对,貌似我们的对象并没有启动,这有可能是被编译器优化掉了。。。= =!
控制台的显示:1
2sxf@sxf-PC:~/data/workspace/C++/OObyCpp/testCppRunCode$ ./main
Main function run!
稍加改动:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17#include <cstdio>
using namespace std;
class test
{
public:
test(const char* msg) {
printf("%s\n",msg);
}
};
test show_message("Hello Ctor!");
int main(){
printf("Main function run!\n");
return 0;
}
好的,我们发现构造函数运行在了main函数之前,也就是我们的类型定义的构造期。1
2
3sxf@sxf-PC:~/data/workspace/C++/OObyCpp/testCppRunCode$ ./main
Hello Ctor!
Main function run!
具体想详细了解C++的运行环境的细节,推荐看一本英文的开源书:
【How to Make a Computer Operating System】
这本书讲解如何利用C++开发了一个小型操作系统,而在C++运行时的导入过程中,就介绍了C++全局对象构造函数的运行过程,可以清楚的看出,C++的主函数在汇编层的调用流程是这样的:
1 | start: |
于是我们可以这样编写一个类,专门用来注册一个类:
1 | class reflector_class { |
这个类的对象在构造时,会去调用ClassRegister类中的静态方法,向其中添加类名和类元数据
我们希望每个类对象能够方便的找到自己的meta_class,最简单的方式就是将其添加为自己的成员,为何不用继承机制呢?首先继承较为复杂,并且父类也同样可能拥有meta_class, 我们希望每个类型都能方便的找到meta_class,那么可以建一条Reflectible宏,让大家写在class中
1 | #ifndef Reflectible |
为了避免放置在最上面时,影响下面成员的private的默认定义,所以写成这样。
我们在写一个宏,让用户添加到类的cpp文件中,真正定义该meta_class对象:1
2
3
4
5#ifndef ReflectClass
#define ReflectClass(class_name) \
IMetaClass* class_name :: meta_class = new MetaClass< class_name >(); \
reflector_class class_name##reflector_class( #class_name , class_name::meta_class)
#endif
这里我们用到了两个宏技巧:
## 表示将两个符号连接在一起,由于词法分析中,宏是按照词的顺序分隔的,如果直接连接,往往会造成符号分析不清。
#something 表示将该内容展开成字符串的形式 => "something data",所以我们可以很方便的用这个宏将宏符号转为字符串传入到函数中。
首先编写一个能调用成员函数的模板类,根据我们的反射原理,将一个函数指针转换为成员函数的指针:1
2
3
4
5
6
7
8
9
10
11
12
13
14class MetaFunc {
public:
MetaFunc(void* p) { func_pointer = p; }
void setFuncPointer(void* p) { func_pointer = p; }
template <typename T, typename R, typename... Args>
R Call(T* that, Args... args) {
R (T::*mp)();
mp = *((R (T::**)())func_pointer);
return (that->*mp)(args...);
}
private:
void* func_pointer;
};
我在这里使用了C++11的新特性,可变参数的模板,这样可以更方便的接受目标参数
如果我们直接对成员函数取地址,返回的是一个return_type (ClassName::)(args)这样的成员函数指针。
注意,成员函数指针不能直接被传递,成员函数指针由于包含了很多其他数据信息,并不能被被强制类型转换成void,一个显而易见的例子是,成员函数指针,往往比较大,最大的指针甚至可以达到20byte。
为了能够传递函数指针,我们可以将成员函数指针赋值给一个该成员函数指针类型的对象,然后再对这个指针对象取地址
1 | auto p = &test::print; |
这个地址是一个指针的指针return_type (ClassName::**)(args)
于是就有了我们前面代码中,强制类型转换的方法
我们目前要将地址传递过来,但是我们并不知道每个类中有多少个函数,所以我们要使用C语言的宏,对可变参数进行处理。
下面将reflector_class进行一下修改,支持多个参数1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23class reflector_class {
public:
reflector_class(const char* name, IMetaClass* meta_class, ...) {
ClassRegister::Add(name, meta_class);
printf("define class: %s\n", name);
va_list ap;
va_start(ap, meta_class);
for (int arg = va_arg(ap, int); arg != -1; arg = va_arg(ap, int) )
{
std::string name(va_arg(ap, const char*));
void* p = va_arg(ap, void*);
if (arg == 0) {
printf("\tdefine func: %s\n", name.c_str());
MetaFunc* f = new MetaFunc(p);
meta_class->AddFunc(name, f);
} else {
printf("\tdefine prop: %s\n", name.c_str());
}
}
va_end(ap);
}
};
va_list ap; 可变参数列表
va_start(ap, meta_class); 这里的第二个参数,是当前函数的最后一个固定参数位置
void* p = va_arg(ap, void*); 可以用来获得一个固定类型的参数
使用过后释放资源:
va_end(ap);
为了支持函数和属性两种声明,我们定义如下宏: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#ifndef DefReflectFunc
#define DefReflectFunc(class_name, func_name) \
auto func_name##_function_pointer = &class_name::func_name
#endif
#ifndef ReflectClass
#define ReflectClass(class_name) \
IMetaClass* class_name :: meta_class = new MetaClass< class_name >(); \
reflector_class class_name##reflector_class( #class_name , class_name::meta_class ,
#endif
#ifndef ReflectFunc
#define ReflectFunc(func_name) \
0, #func_name, _F(func_name##_function_pointer) ,
#endif
#ifndef ReflectProp
#define ReflectProp(prop_names) \
1, #prop_names, _F(prop_names) ,
#endif
#ifndef _F
#define _F(x) reinterpret_cast<void*>(&x)
#endif
#ifndef End
#define End -1 )
#endif
这样我们在cpp中使用这些宏时只需要:1
2
3
4DefReflectFunc(test2,print2);
ReflectClass(test2)
ReflectFunc(print2)
End;
好的,关键的部分已经都清楚了,但目前我们还欠缺一个很重要的类,就是类的全局注册器。
1 | class ClassRegister |
但这个类有一个严重的漏洞,会造成程序崩溃,我们在接下来的章节中,将会介绍这个尴尬的问题的发生原因。
Welcome to Hexo! This is your very first post. Check documentation for more info. If you get any problems when using Hexo, you can find the answer in troubleshooting or you can ask me on GitHub.
1 | $ hexo new "My New Post" |
More info: Writing
1 | $ hexo server |
More info: Server
1 | $ hexo generate |
More info: Generating
1 | $ hexo deploy |
More info: Deployment
本专栏文章列表
一、何为面向对象
二、C语言也能实现面向对象
三、C++中的不优雅特性
四、解决封装,避免接口
五、合理使用模板,避免代码冗余
六、C++也能反射
七、单例模式解决静态成员对象和全局对象的构造顺序难题
八、更为高级的预处理器PHP
九、Gtkmm的最佳实践
本系列文章由 西风逍遥游 原创,转载请注明出处:西风广场 http://sunxfancy.github.io/
上回书说道,我们的程序有一个隐藏的漏洞,如果ClassRegister这个类所在的.o文件,如果在所有.o文件中是第一个被链接的的,那么就不会出问题。
这么说太抽象了,让我们画个图表
1 | ClassRegister.o |
这样的结构,也就是链接顺序要这样指定1
gcc -o main ClassRegister.o Meta.o Main.o
就不会出问题,但如果调换一下顺序就可能出问题。
思考一下原因,ClassRegister中的map对象,是一个全局对象,而我们注册类的时候,也使用了全局对象的构造函数,两个谁先执行呢?这个就不得而知了,C++并未说明两个谁先谁后,而一般链接器,都是从前往后链接代码,而构造函数的执行顺序,也往往和链接时的顺序有关。
但这样实现就很不好,我们的系统居然要靠链接器的顺序才能正确编译执行,太不可靠了,万一用户没注意到这一点,直接编译链接,就会出现未知的错误。
那么如何避免这种情况呢?
C++中有一种极好的设计模式很适合这种情况,那就是用单例,单例模式也很容易理解,核心就是推迟构造,如果没有使用时,就不会被构造,被用到时,对象就会构造,并且仅一次,最常见的写法就是:
1 | class CSingleton |
当然,我们这里并没有考虑多线程,因为多线程时单例模式一般要加锁来保障不会多次构造引发冲突。
于是经过简要修改,就能用单例模式设计一个类注册器了:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20class ClassRegister
{
private:
ClassRegister() { printf("register\n"); } //构造函数是私有的
std::map<const std::string, IMetaClass*> class_map;
public:
static ClassRegister * GetInstance() {
static ClassRegister instance; //局部静态变量
return &instance;
}
static void Add(const std::string s, IMetaClass* k) {
GetInstance()->class_map[s] = k;
}
static IMetaClass* Get(const std::string s) {
std::map<const std::string, IMetaClass*>& m = GetInstance()->class_map;
if (m.find(s) != m.end()) return m[s];
else return NULL;
}
};
这个类注册器简单实用,采用的设计方式和指针的模式稍有不同,这里用到了局部静态变量的概念。
局部静态变量能够改变对象的生存周期,这样就能很好的符合我们的要求。