一、引子
為什麼要浪費時間去設計一個算法來實現數據的文件存儲還要費勁地調試代碼呢?Boost庫可以為你做這些事情。借助於串行化模板,你可以容易地把數據存儲到你自己定制格式的文件中。本文將教給你如何輕松地存儲數據並回讀數據。
二、概述
當你開發一個軟件包時,你總是想集中精力於軟件的功能。而最讓你擔心的是,花費大量的時間寫代碼,而該代碼有可能會應用在另外大量的其他程序中。那正是重用的含義所在,你會希望另外某人已經為你編寫出這樣現成的代碼。
這類問題的一個很好的例子是給予你的程序存檔的能力。例如,你可能在寫最偉大的天文學程序-在該程序中,你的用戶可以輕易地輸入時間和坐標,你的程序負責繪制當前天空的地圖。但是,假定你賦予你的用戶能夠高亮某些星星,這樣以來它們可以容易地突出在地圖上。最後,你讓用戶能夠保存他們的配置以備後用。
你的程序集中於天文學編程。你並不是在寫一個通用庫來保存文檔,所以你不必把大量的時間花在存儲功能上,因為你要專注於程序的天文學特性。如果你是用C++編程,你可以從Boost重用庫得到幫助。為了保存文件,Boost庫包括一個串行化類,正是你需要的。
如果你成功地創建了你的程序工程,很可能有一個類來包含用戶信息或文檔。例如,你可能有一個類,該類列舉用戶們最喜歡的星星的名字和位置。(請原諒這裡的簡化)。這就是你希望用戶能夠保存到磁盤上的數據。畢竟,幾乎所有的程序都有文件保存功能。微軟的Word保存文本和格式化數據,而Excel保存工作單數據。一個優秀的地圖程序可以用戶保存喜歡的位置,GPS路線,旅程,等等。
借助於Boost串行化庫的幫助,實現保存很容易-所要做是僅僅是設置好你的類,而由庫來負責其它一切-使你專注於真正的工作。
其思想是很簡單的:你創建了一個包含用戶數據的對象。當准備保存信息時,用戶選擇File|Save As,然後從文件對話框中選擇一個文件名即可。借助於Boost,你的程序就把數據保存到選定的文件中。以後,當用戶重新啟動該程序時,選擇File|Open,選定已保存的文件,你的程序再一次使用Boost-但是這一次重新裝入數據,因此,重新產生了該對象。瞧,用戶數據被回復了!或者,從用戶的角度來看,文檔已被打開。
下面的例子只是簡單地演示保存和加載一些圖形類。第一個類,Vertex,描述了一個二維的點。第二個類,Polygon,包含一個Vertex實例的容器。第三個類,Drawing,包含一個Polygon的容器。
想把所有這些都保存到一個文檔中去無疑是一個惡夢-這不是花費時間的地方-你要實現最好的圖形程序設計,因為這是你的專長。好了,讓Boost庫為你做其它一切吧。
三、串行化一個類
首先,考慮一下Vertex類。該類是最容易串行化的一個,因為它不包含其它對象。該類包含兩個值,x和y,且都是double型。我還給該類定義了幾個存取x和y的函數,還有一個dump函數,它負責把x和y的值輸送到控制台。最後,我包含了兩個構造器,一個是缺省的,另一個用作輸入參數。(為了簡化起見,該例程並沒有做任何實際的繪圖。抱歉!)
下面最吸引人的部分是必需的代碼行以串行化該類。下面就是該類(注意粗體部分):class Vertex {
private:
friend class boost::serialization::access;
template
void serialize(Archive & ar, const unsigned int version)
{
ar & x;
ar & y;
}
double x;
double y;
public:
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;
}
};
注意在程序的最後,我沒有實際地使用缺省的構造器Vertex(),但是串行化庫的確調用了它,因此我需要把它包含進去。
串行化部分首先串行化庫存取私有成員,特別是接下來的串行化函數。串行化庫的創建者Robert Ramey指出,你不需要任何的函數,包括在派生類中的那些,調用你的串行化方法;只需由串行化庫來調用即可。因此,為了保護你的類,需要把串行化功能聲明為私有的,然後允許有限制地存取該串行化庫,這通過把類boost::serialization::access聲明為你的類的友元來實現,見代碼。
接下去是串行化函數,它是一個模板函數。如果你對模板還不太熟悉的話,不要緊:你不需要理解模板部分而照舊可以使之工作。然而,必須確保你理解了串行化功能的核心:ar & x;
ar & y;
首先,讓我聲明一下:這兩行代碼並不是聲明參照引用變量,雖然形式上看上去相似。代之的是,它們調用一個&操作符,並且把你的類成員寫入到文件中或者把它們讀進來。是的,你已經正確地認出了;該功能實現了一石二鳥(或者更准確地說,用一套代碼完成了兩件任務)的功效。當你在把一個Vertex對象保存到一個文件中去時,串行化庫調用這個串行化功能;第一行把x的值寫入到文件中,第二行把y的值寫入到文件中。後來,當你把一個Vertex對象從文件中讀回時,第一行實現從文件中讀回x值,第二行實現從文件中讀回y值。
這是某種特別的操作符重載!事實上,&字符是一個在串行化庫內部定義的一個操作符。幸好你不需要理解它是如何工作的。
好,就是那麼簡單。下面是一些示例代碼,你可以試著把一個Vertex 對象保存到一個文件中:Vertex v1(1.5, 2.5);
std::ofstream ofs("myfile.vtx");
boost::archive::text_oarchive oa(ofs);
oa << v1;
ofs.close();
就是這樣!第一行產生Vertex對象。下面的四行打開一個文件,把一個特定的串行化類與文件相結合,然後寫向文件,最後關閉文件。下面是一段把一個Vertex 對象從文件中讀入的代碼:Vertex v2;
std::ifstream ifs("myfile.vtx", std::ios::binary);
boost::archive::text_iarchive ia(ifs);
ia >>v2;
ifs.close();
v2.dump();
這段代碼生成一個Vertex的實例,然後再打開一個文件(這次是為讀取的目的),把一個串行化類與文件相關聯,把對象讀進來,然後關閉文件。最後,代碼輸出Vertex的值。如果你把前面的這兩個程序段放在一個main函數中並運行,你會看到輸出兩個原始值:1.5和2.5。
注意
注意我使用的文件擴展名是:.vtx。這並不是一個專門的擴展名;它是我自己定制的擴展名。這聽起來有點愚蠢和瑣碎,但是實際上,我們是在創建自己的文件格式。為了指出這一特殊的文件格式,我使用了擴展名叫.vtx,其意指Vertex。
四、串行化容器
在我的示例中,一個繪圖對象可以包含多個多邊形對象(我把它們存儲在一個向量中,該向量是標准庫容器模板的一員),每一個多邊形對象可以包含多個對象Vertex(我也用向量存儲它們)。
串行化庫包括保存數組和容器的功能。因為你可以把指針存儲到數組中,串行化庫也支持指針。請考慮一下:如果你有一個包含Vertex指針的數組,而且你直接把該數組寫入一個文件中,你就會有一堆指針存儲在文件中,而不是實際的Vertex 數據。那些指針僅是些數字(內存位置),當後面接著回讀數據時它們是毫無意義的。所以,該庫十分聰明地從對象中抓取了數據而不是指針。
考慮到存儲作為容器的對象,你要把每一個類串行化。在串行化方法中,你可以讀取和寫入容器,就象你操作另外一個成員一樣。你的容器可以是簡單的語言本身內存的數組(如Vertex *vertices[10];),或者是來自於標准庫的容器。因為現在是21世紀,我喜歡緊跟時代的步伐,所以我在本例中選擇使用標准庫。
盡管你可以在你的串行化類中編寫代碼,針對容器和每一個成員;然而,你不必這樣做。作為代勞,庫已十分聰明地自動遍歷容器了。你所有要做是僅是寫出容器,如下,其中vertices是一個容器:ar & vertices;
讓庫來做其余的工作吧。相信嗎?下面是類Polygon的代碼,串行化部分以粗體標出:class Polygon {
private:
vector
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));
}
};
首先,請注意我用一個矢量來存儲布點。(如果你對模板還是個新手,不要緊,只需要把vectorar & vertices;
兩個公共方法的建立,可以用來十分方便地操作該多邊形。第一個addVertex方法,讓你把另外一個結點添加到該多邊形上;它使用了push_back方法,這是把一項加到一個矢量上去的標准方法。Dump函數遍歷該矢量,把每一個矢量寫到標准輸出設備上去