日期:2013-07-15  浏览次数:20463 次


 

 
 
C++永世对象存储 (Persistent Object Storage for C++)
简介 描述对象类型 从存储器中分配和释放对象 永世对象协议 存储器结构函数 打开存储器 POST++ 的安装 POST++ 类库 和 POST++一同使用 STL 类 替换标准分配子 如何使用 POST++ S调试 POST++ 使用的细节 关于 POST++ 更多的一些信息 简介
POST++ 提供了对使用对象的简单无效的存储. POST++ 基于内存文件镜像机制和页面镜像处理。POST++ 消弭了对永世对象访问的开销. 此外 POST++ 支持多存储,虚函数, 数据更新原子操作, 高效的内存分配和为指定释放内存方式下可选的垃圾收集器. POST++ 同样可以很好的任务在多承继和包含指针的对象上。

 
描述对象类型
POST++ 存储管理需求一些信息以使永世对象类型支持垃圾收集器,装载时援用重定位和初始化虚表内函数指针。但不幸的是C++言语没有提供运转时从类中或许这些信息的机制。为了避免使用一些特殊的工具(预处理器)或“脏哄骗”途径(从调试信息中获取类信息),这些信息必须由程序员来指明。这些称为类注册器的东西可以简单的通过POST++提供的一些宏来实现。

POST++ 在从存储器重载入对象时调用缺省结构函数来初始化对象。为了使对象句柄能够存储,程序员必须在类定义中包含宏 CLASSINFO(NAME, FIELD_LIST) . NAME 指明对象的名字。 FIELD_LIST 描述类的的援用字段。在头文件 classinfo.h 定义了三个宏用于描述字段:
REF(x) 描述一个字段. REFS(x) 描述一个一维固定数组字段。. (例如:定长数组). VREFS(x) 描述可变一维数组字段。可变数组只能是类的最后一个成员。当你定义类的时候,你可以指定一个仅包含一个元素的数组。具体对象实例中的元素个数可以在生成时指定。
这些宏列表必须用空格分开: REF(a) REF(b) REFS(c). 宏 CLASSINFO 定义了缺省结构函数 (没有参数的结构函数) 和类描述符. 类描述符是类的一个静态成员名为 self_class. 这样类 foo 的描述符可以通过 foo::self_class 访问. 基类和成员的缺省结构函数会被编译器自动调用,你不必担心需求明确调用他们。但是对于序列化的类中的结构成员不要忘记在结构定义中使用 CLASSINFO 宏。然后通过存储器管理注册该类使其可被访问。这个过程由宏 REGISTER(NAME) 完成。类名将和对象一同放在存储器中。在打开存储器的时候类在存储和使用程序之间被镜像。存储器中的类名和程序中的类名进行比较。如果有类没有被程序定义或使用程序和存储器中的类有不同的大小,程序断言将失败。

下面的例子阐述了这些规则:

struct branch { object* obj; int key; CLASSINFO(branch, REF(obj));};class foo : public object { protected: foo* next; foo* prev; object* arr[10]; branch branches[8]; int x; int y; object* childs[1]; public: CLASSINFO(foo, REF(next) REF(prev) REFS(arr) VREFS(linked)); foo(int x, int y);};REGISTER(1, foo);main() { storage my_storage("foo.odb"); if (my_storage.open()) { my_root_class* root = (my_root_class*)my_storage.get_root_object();if (root == NULL) { root = new_in(my_storage, my_root)("some parameters for root");}... int n_childs = ...;size_t varying_size = (n_childs-1)*sizeof(object*);// We should subtract 1 from n_childs, because one element is already// present in fixed part of class. foo* fp = new (foo:self_class, my_storage, varying_size) foo(x, y);...my_storage.close(); }}

 
从存储器中分配和释放对象
POST++ 为了管理存储内存提供了特别的内存分配子. 这个分配子使用两种不同的方法: 针对分配小对象和大对象。所有的存储内存被划分为页面(页面的大小和操作系统的页面大小无关,目前版本的 POST++ 中采用了 512 字节). 小对象是这样一些对象,他们的大小小于或等于256字节(页面大小/2). 这些对象被分配成固定大小的块链接起来。每一个 链包含相反大小的块。分配对象的大小以8个字节为单位。为每个对象分配的包含这些块大小为256的的链的数量最好不要大于14(不同的均衡页面数). 在每个对象之前 POST++ 分配一个对象头,包含有对象标识和对象大小。考虑到头部刚好8个字节,并且在C++中对象的大小总大于0,大小为8的块链可以舍弃。分配和释放小对象通常情况下是非常快的: 只需求从L1队列中进行一次插入/删除操作. 如果链为空并且我们试图分配新的对象,新页被分配用来存储像目前大小的对象(页被划分成块添加到链表中)。大对象(大于256字节)所需求的空间从空闲页队列中分配。大对象的大小和页边界对齐。POST++ 使用第一次喂给随机定位算法维护空闲页队列(所有页的空闲段按照地址陈列并用一个特别的指针跟随队列的当前位置)。存储管理的实现见文件 storage.cxx

使用显式还是隐含的内存释放取决于程序员。显式内存释放要快(特别是对小对象而言)但是隐含内存释放(垃圾收集)愈加可靠。在 POST++ 中使用标志和清除垃圾收集机制。在存储中存在一个特别的对象:根对象。垃圾收集器首先标志所有的对象可被根对象访问(也就是可以从根对象到达,和通过援用遍历)。这样在第一次GC阶段所有未被标志的对象被释放。垃圾收集器可以在对象从文件载入的时候生成(如果你传递 do_garbage_collection 属性给 storage::open() 方法)。也可以在程序运转期间调用 storage::do_mark_and_sweep() 方法调用垃圾收集器。但是请务必确定没有被程序变量指向的对象不可从根对象访问(这些对象将被GC释放)。

基于多承继C++类在对象中可以有非零偏移并且对象内也可能有援用。这是我们为什么要使用特别的技术访问对象头的缘由。POST++ 维护页分配位图,其中每一个位对应存储器中的页。如果一些大对象分配在几个页中,所有这些对象占用的页所对应的位除了第一个外都被置为1。所有其他页在位图中有对应清空位。要找到对象起始地址,我们首先按页大小陈列指针值。然后 POST++ 从位图中查找对象起始页(该页在位图中有零位)。然后从页开始处包含的对象头中取出对象大小的信息。如果大小大于页大小的一半那我们曾经找到了对象描述:它在该页的开始处。反之我们计算页中所使用的固定块的大小并且把页中指针偏移按块大小计算出来。这种头部定位方案被垃圾收集器使用,类 object 定义了 operator delete,和被从对象头部解析出对象大小和类信息的方法使用。

在 POST++ 中提供了特别重载的 new 方法用于存储中的对象分配。这个方法需求创建对象的类描述,创建对象的存储器,以及可选的对象实例可变部分的大小作为额外的参数。宏 new_in(STORAGE, CLASS) 提供永世对象创建“语法糖”。永世对象可以被重定义的 operator delete 删除。
永世对象协议
在 POST++ 中所有的永世对象的类必须承继自 object.h 中定义的类 object 。这个类不含任何变量并提供了分配/释放对象及运转时得到类信息和大小的方法。类 object 可以是多承继中一个基类(基类的次序无所谓)。每一个永世类必须有一个供POST++ 系统使用的结构函数(见 Describing object class 一节)。这意味着你不能使用没有参数的结构函数来初始化。如果你的类结构函数甚至没有有意义的参数,你必须加一个虚拟的以和宏 CLASSINFO 创建的结构函数区别开来。

为了访问永世存储器中的对象程序员需求某种根对象,通过它可以使用普通的C指针访问到每一个其他对象。POST++ 存