四、串行化容器
在我的示例中,一个绘图对象可以包含多个多边形对象(我把它们存储在一个向量中,该向量是标准库容器模板的一员),每一个多边形对象可以包含多个对象Vertex(我也用向量存储它们)。
串行化库包括保存数组和容器的功能。因为你可以把指针存储到数组中,串行化库也支持指针。请考虑一下:如果你有一个包含Vertex指针的数组,而且你直接把该数组写入一个文件中,你就会有一堆指针存储在文件中,而不是实际的Vertex 数据。那些指针仅是些数字(内存位置),当后面接着回读数据时它们是毫无意义的。所以,该库十分聪明地从对象中抓取了数据而不是指针。
考虑到存储作为容器的对象,你要把每一个类串行化。在串行化中,你可以读取和写入容器,就象你另外一个成员一样。你的容器可以是简单的语言本身内存的数组(如Vertex *vertices[10];),或者是来自于标准库的容器。因为现在是21世纪,我喜欢紧跟时代的步伐,所以我在本例中选择使用标准库。
尽管你可以在你的串行化类中编写代码,针对容器和每一个成员;然而,你不必这样做。作为代劳,库已十分聪明地自动遍历容器了。你所有要做是仅是写出容器,如下,其中vertices是一个容器:
ar & vertices;
让库来做其余的工作吧。相信吗?下面是类Polygon的代码,串行化部分以粗体标出:
class Polygon {
private:
vectorvertices;
friend class boost::serialization::access;
template
void serialize(Archive & ar, const unsigned int version)
{
ar & vertices;
}
public:
void addVertex(Vertex *v) {
vertices.push_back(v);
}
void dump() {
for_each(vertices.begin(), vertices.end(), mem_fun(&Vertex::dump));
}
};
首先,请注意我用一个矢量来存储布点。(如果你对模板还是个新手,不要紧,只需要把vector当作是存储指向Vertex 实例的指针的矢量就行,因为其实际上就是如此。)。下一步,在串行化函数中,我不想遍历该矢量-写每一个成员。相反,我只是读写整个矢量即可:
ar & vertices;
两个公共方法的建立,可以用来十分方便地操作该多边形。第一个addVertex方法,让你把另外一个结点添加到该多边形上;它使用了push_back方法,这是把一项加到一个矢量上去的标准方法。Dump函数遍历该矢量,把每一个矢量写到标准输出设备上去。即使对一些很有经验的C++老手,也可能对下面这一行不太熟悉:
for_each(vertices.begin(), vertices.end(), mem_fun(&Vertex::dump));
这里用了点小技巧。它不是串行化库的一部分;它是标准库的一部分,对于今天的C++程序可以放心使用,没有任何多余的副作用和经济问题。单词for_each实际上是一个函数,它有三个参数:在容器中的起始位置,结束条件以及一个操作容器中每一项都要调用的函数(依赖于你的C++程序实现,你可以要包括头文件如,’#include ’来得到for_each函数。我使用的是GNU库,所以用’#include ’语句)。在我的例子中,我用的for_each函数的第三个参数是Vertex 类的dump成员函数。但是有一个问题:你不能只调用一个成员函数本身;你要从一个具体的对象中调用才行。这正是成员函数mem_fun的来源所在。它是一个专门函数(标准库的一部分),在此与函数for_each一起工作,负责调用具体对象的dump函数。也就是说,它把dump()绑定到for_each当前操纵的Vertex对象上。
为简化起见,这里的for_each调用遍历整个列表中的每一个Vertex,并调用Vertex::dump-所有这些只有一行代码!
接下来,Drawing类实际上与Polygon类很相似,除了它包含一些Polygon 对象,而不是包含一个Vertex对象的容器。不是一个大问题。
下面是完整的程序,包含一些额外的析构器以用于清理内存:
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
class Vertex {
private:
//串行化代码开始
friend class boost::serialization::access;
template
void serialize(Archive & ar, unsigned int version)
{
ar & x;
ar & y;
}
//结束串行化代码
double x;
double y;
public:
Vertex() {} //串行化需要一个缺省的构造器
~Vertex() {}
Vertex(double newX, double newY) : x(newX), y(newY) {}
double getX() const { return x; }
double getY() const { return y; }
void dump() {
cout << x << " " << y << endl;
}
};
void delete_vertex(Vertex *v) { delete v; }
class Polygon {
private:
vectorvertices;
friend class boost::serialization::access;
template
void serialize(Archive & ar, const unsigned int version)
{
ar & vertices;
}
public:
~Polygon() {
for_each(vertices.begin(), vertices.end(), delete_vertex);
}
void addVertex(Vertex *v) {
vertices.push_back(v);
}
void dump() {
for_each(vertices.begin(), vertices.end(), mem_fun(&Vertex::dump));
}
};
void delete_poly(Polygon *p) { delete p; }
class Drawing {
private:
vectorpolygons;
friend class boost::serialization::access;
template
void serialize(Archive & ar, const unsigned int version)
{
ar & polygons;
}
public:
~Drawing() {
for_each(polygons.begin(), polygons.end(), delete_poly);
}
void addPolygon(Polygon *p) {
polygons.push_back(p);
}
void dump() {
for_each(polygons.begin(), polygons.end(), mem_fun(&Polygon::dump));
}
};
string getFileOpen() {
//在实际开发中,这将调用一个各种样的FileOpen 对话框
return "c:/myfile.grp";
}
string getFileSaveAs() {
//在实际开发中,这将调用一个各种样的FileSave 对话框
return "c:/myfile.grp";
}
void saveDocument(Drawing *doc, const string &filename) {
ofstream ofs(filename.c_str());
boost::archive::text_oarchive oa(ofs);
oa << *doc;
ofs.close();
}
Drawing *openDocument(const string &filename) {
Drawing *doc = new Drawing();
std::ifstream ifs(filename.c_str(), std::ios::binary);
boost::archive::text_iarchive ia(ifs);
ia >>*doc;
ifs.close();
return doc;
}
int main()
{
Polygon *poly1 = new Polygon();
poly1->addVertex(new Vertex(0.1,0.2));
poly1->addVertex(new Vertex(1.5,1.5));
poly1->addVertex(new Vertex(0.5,2.9));
Polygon *poly2 = new Polygon();
poly2->addVertex(new Vertex(0,0));
poly2->addVertex(new Vertex(0,1.5));
poly2->addVertex(new Vertex(1.5,1.5));
poly2->addVertex(new Vertex(1.5,0));
Drawing *draw = new Drawing();
draw->addPolygon(poly1);
draw->addPolygon(poly2);
//演示保存一个文档
saveDocument(draw, getFileSaveAs());
// 演示打开一个文档
string filename2 = getFileOpen();
Drawing *doc2 = openDocument(getFileOpen());
doc2->dump();
delete draw;
return 0;
}
记住:我尽力脱离开把绘图对象写入到文件中去的思想。代之的是,我只是在概念上把绘制对象当作我的文档,然后存储文档文件和读回它们。那些文档文件都具有我为我的程序创立的专门格式,而且我给予它们唯一的文件扩展名.grp,其含义指图形。
另外,我创建了几个帮助函数:getFileSaveAs和getFileOpen。在本例中这些函数仅返回一个硬编码的字符串形式的文件名。在实际开发中,这些函数一般会分别在菜单项File|Save As和File|Open中被调用;并将会调用系统的File|Open和File|Save对话框。这些对话框将返回一个用户想使用的字符串形式的文件名。这样,用户的看法就同我们一样了:打开和保存文档,而不是读取和写绘图对象数据到文档中。要看清它们在概念上的区别,虽然它们在功能上是相同的。
五、小结
借助于Boost库,给你的软件增加文件的保存/打开功能相当容易。如果你想自己试验这些代码,你可以在官方站点该Boost库,下载最新版本试验。
六、备注
要使用串行化库,你最少需要得到该库的1.32.0版本(早期的版本不包括串行化库)。注意,在此我不是向你介绍如何安装Boost库;上提供详细步骤说明如何安装该库。如果你使用该串行化库,你还需要编译另外几个该库需要的.cpp源文件-你可以在boost_1_32_0libsserializationsrc文件夹下找到它们。还有一个boost_1_32_0libsserializationbuild 库,它使用了一种新的创建(build)系统,称为jamfile,你可以用它来把源文件创建成一个库。或者,你可以仅把这些源文件添加到你的工程的src目录下。