萬盛學電腦網

 萬盛學電腦網 >> 網絡編程 >> 編程語言綜合 >> C++永久對象存儲

C++永久對象存儲

  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++ 存儲器提供了兩個方法用於指定和得到根對象的引用:

  void set_root_object(object* obj); object* get_root_object();

  當你創建新存儲時 get_root_object() 返回 NULL。你需要通過 set_root_object() 方法創建根對象並且在其中保存引用。下一次你打開存儲時,根對象可以通過 get_root_object() 得到。

  提示:在實際應用中類通常在程序開發和維護過程中被改變。不幸的是 POST++ 考慮到的簡單沒有提供自動對象轉換的工具(參見 GOODS 中的懶惰對象更新設計示例),所以為了避免添加新的字段到對象中,我只能建議你在對象中保留部分空間供將來使用。這對根對象來說意義尤其重大,因為它是新加入對象的優選者。你也需要避免轉換根對象的引用。如果沒有其他對象含有指向根對象的引用,那麼根對象可以被簡單的改變(通過 set_root_object 方法)到新類的實例。POST++ 存儲提供設置和取得村出版標識的方法。這個標識可以用於應用根據存儲器和應用的版本來更新存儲器中對象。

  存儲器構造函數你可以在應用中同時使用幾個存儲器。存儲器構造函數有一個必需的參數 - 存儲文件路徑。如果這個文件沒有擴展名,那麼 POST 為文件名添加一個後綴“。odb”。這個文件名也被 POST++ 用於形成幾個輔助文件的名字:

  文件描述使用時機後綴包含新存儲器映像的臨時文件用於非事務處理模式下保存存儲器新映像".tmp"事務記錄文件用於事務模式下保存鏡像頁面".log"保存存儲器文件備份僅用於Windows-95下重命名臨時文件".sav"

  存儲器構造函數的另兩個參數具有缺省值。第一個參數 max_file_size 指出存儲器文件擴展限制。如果存儲器文件大於 storage::max_file_size 那麼它不會被切除但是也不可能更進一步的擴展。如果 max_file_size 大於文件大小,行為依賴於打開存儲器的模式。在事務模式下,文件在讀寫保護下被鏡像到內存中。Windows-NT/95 擴展文件大小到 max_file_size。文件大小被 storage::close() 方法縮短到存儲器中最後一個對象的邊界。在 Windows 中為了以讀寫模式打開存儲器需要在磁盤上至少有 storage::max_file_size 的空閒字節數即使你不准備向其中加入新對象。

  存儲

copyright © 萬盛學電腦網 all rights reserved