提供執行時期物件屬性資料的保存和恢復機制,這機制嚴格要求物件須是 cxxlPreserve 的延伸類別,而保存的容器須是 IcxxlStorage 的延伸類別,目前提供的保存容器實作有 MemStorage 和 TxtStorage 兩種。
成為 cxxlPreserve 元件須做三件事:
- 須實作 cxxlPreserve 宣告的 Ref(Filter *F) 永續存取虛擬函數。
- 須用 PSmart_Ptr 或 PSmart_Set 連結 cxxlPreserve 子物件(包括 NULL 物件)。
- 同步控制須使用 cxxlPreserve 提供的 csPreserve,這樣在永續存取時才能做好同步控制。
PSmart_Ptr 像 Smart_Ptr 一樣都是智慧指標,但 PSmart_Ptr 才能讓 cxxlPreserve 子物件有永續存取能力,PSmart_Ptr 只可以用在連結 cxxlPreserve 子物件作為屬性之用,若要作為參數的傳遞仍得透過 Smart_Ptr。
PSmart_Set 可以包裹不定數目的 cxxlPreserve 子物件,而 PSmart_Ptr 只包裹一個,但 PSmart_Ptr 在擁有者的內部內可如 Smart_Ptr 一樣使用,而 PSmart_Set 須取出包裹的列表(cxxlList)才能使用。
接下來分階段使用範例做說明:
範例一
以下是一個簡單的典型 cxxlPreserve 元件範例:
#include <sstream>
#include <iostream>
#include <locale>
#include <MEMSTORAGE.HPP>
#include <TXTSTORAGE.HPP>
using namespace std;
using namespace CxxlMan;
class A:virtual public cxxlPreserve
{
// 這個 class 只包含兩個簡單屬性
double HP;
float MP;
protected:
// 各層的 cxxlPreserve 延伸類別都須覆寫這個虛擬函數,
// 而且不須做同步控制的處理
virtual bool cxxlFASTCALL Ref(Filter *F)
{
if(F != NULL && cxxlPreserve::Ref(F))
{
// 要永續儲存的資料是一般型別的單一個體或固定長度的陣列或 NULL,
// 在 存 和 取 的都可以採用 Filter::Ref()
if(F->Ref(&HP,1,L"A.HP") == false)
return false;
if(F->Ref(&MP,1,L"A.MP") == false)
return false;
return true;
}
else
return false;
}
public:
A(ISpirit *spirit = Spirit_Easy)
:cxxlObject(spirit)
{
HP = 200.2;
MP = 100.1;
}
virtual ~A() {}
void Set(float hp, float mp)
{
// cxxlPreserve 的延伸類別都須使用 cxxlPreserve::csPreserve 做同步控制
csPreserve.Enter();
HP = hp;
MP = mp;
csPreserve.Leave();
}
double GetHP()
{
// cxxlPreserve 的延伸類別都須使用 cxxlPreserve::csPreserve 做同步控制
CriticalSecHandle AutoCSH(csPreserve);
return HP;
}
float GetMP()
{
// cxxlPreserve 的延伸類別都須使用 cxxlPreserve::csPreserve 做同步控制
CriticalSecHandle AutoCSH(csPreserve);
return MP;
}
};
編寫 cxxlPreserve 的延伸類別最費心的就在 Ref(Filter *F) 的覆寫,要做儲存和讀取時都會叫用這個函數,只不過儲存時 F 是 class SaveFilter 的實例,讀取時 F 是 class LoadFilter 的實例,兩者都是 class Filter 的延伸類別,在範例一因資料簡單,所以只用 Filter::Ref() 做儲存和讀取的動作就可以了。
範例二
延續自範例一做進一步的示範
class C:public A
{
long HP;
int MP;
// str 資料長度是不固定的
UNICODE_String str;
UNICODE_String str_bp; // 這個在 Ref() 中用來為 str 做暫時性的處理
PSmart_Ptr
<A> A_Ptr; // cxxlPreserve 子物件要這個代替 Smart_Ptr 才能自動做永緒儲存
protected:
virtual bool cxxlFASTCALL Ref(Filter *F)
{
if(F != NULL && A::Ref(F))
{
if(F->Ref(&HP,1,L"C.HP") == false)
return false;
// 為了處理 str 所以在永續儲存的資料
存
和
取
要分開處理
LoadFilter *pLoadFilter = dynamic_cast<LoadFilter*>(F);
if(pLoadFilter == NULL) // 表示 F 是 SaveFilter
{
F->Ref((wchar_t *)(const wchar_t *)str,str.StrLength()+1,L"C.STR");
}
else // F 是 LoadFilter
{
if(pLoadFilter->isChk()) // 檢查階段
{
const wchar_t *data;
size_t Count;
if(pLoadFilter->LoadChk(&data,&Count,L"C.STR") == false)
return false;
UNICODE_String Tmp(Count);
str_bp = Tmp;
}
else // 實際讀取階段
{
// 在這個階段 Count、Name 和 回傳值都不具意義
pLoadFilter->Ref((wchar_t *)(const wchar_t *)str_bp,0,L"C.STR");
str = str_bp;
}
}
if(F->Ref(&MP,1,L"C.MP") == false)
return false;
return true;
}
else
return false;
}
public:
C(ISpirit *spirit = Spirit_Easy)
:cxxlObject(spirit),
str(L"原始字串"),
A_Ptr(new A(spirit),this,L"A") // PSmart_Ptr 要為子物件指定名稱
{
HP = 300;
MP = 400;
}
virtual ~C() {}
void Set(float hp, float mp)
{
A_Ptr->Set(hp*3,mp*3);
csPreserve.Enter();
A::Set(hp*2,mp*2);
HP = hp;
MP = mp;
csPreserve.Leave();
}
long GetHP()
{
CriticalSecHandle AutoCSH(csPreserve);
return HP;
}
int GetMP()
{
CriticalSecHandle AutoCSH(csPreserve);
return MP;
}
void SetStr(const UNICODE_String &S)
{
str = S;
}
UNICODE_String GetStr() const
{
return str;
}
void Show()
{
cout << "A::HP = " << A::GetHP() << "\tA::MP = " << A::GetMP() << endl
<< "A->HP = " << A_Ptr->GetHP() << "\tA->MP = " << A_Ptr->GetMP() << endl
<< "C::HP = " << GetHP() << "\tC::MP = " << GetMP() << endl;
wcout << "C.str = " << (const wchar_t *)str << endl;
}
};
class C 以繼承和 PSmart_Ptr 持有的方式來使用 class A,若不使用 PSmart_Ptr 來連結子物件,將沒法自動對子物件做永續存取的處理,PSmart_Ptr 和 Smart_Ptr 的用法相當,只不過多了一個檢查名稱。
A 和 C 兩個 class 都屬於 cxxlPreserve 永續元件,所以要覆寫 Ref(Filter *F) 函數,且一定要再宣告成虛擬函數,這樣才能一直繼續延伸下去,實作上有以下幾點須注意:
- 須要再呼叫父類別的 Ref(Filter *F) 函數
- 不須使用 csPreserve 處理同步控制,因 IcxxlStorage 會處理
- 只能用來保存基本形別資料,可參閱 Filter::Ref() 可用的形別
- 保存的資料有一個檢驗名稱,不是識別用的,所以有名稱相同也沒問題
不管是儲存還是讀取都會呼叫 Ref(Filter *F),若要永續儲存的資料元素個數是不變的,可以用 Filter::Ref() 就行了,在做儲存時,Filter 其實是 class SaveFilter。在做讀取時 Filter 則是 class LoadFilter,在做讀取時 Ref(Filter *F) 會被呼叫兩次,第一次為讀取檢驗階段,會把 LoadFilter::Ref() 提供的 Count 及 Name,和儲存的資料比對看是不是相符,若有任何一個不相符, Ref(Filter *F) 應回覆 false;若所有的 cxxlPreserve 類別都回覆 true,那麼第二次呼叫, Filter::Ref() 就實際去執行複製取回資料的動作,否則 Ref(Filter *F) 的 F 會是一個 NULL。
但資料的元素個數若是變動的,即物件某個屬性目前資料的空間,有可能會和相應的永續儲存資料空間大小不一樣,上述的做法就行不通了,LoadFilter 另外提供一個 LoadChk() 可以在讀取檢驗階段使用,LoadChk() 只做 Name 檢驗,若符合就會取得永續儲存資料的實際位址和元素個數,這樣就可以備好空間在實際讀取階段時複製,如範例二中 class C 中 Ref() 對 str 的處理方式。會提供資料的位址是考量到可做其他進一步的檢驗,千萬不要去企圖更改或保存這個位址
關於檢驗名稱
永續存取概念上類似資料流,依賴的是正確的存取順序,所以在 PSmart_Ptr 和 Filter::Ref() 所要求的名稱和存取無關,甚至可用空字串,或相同的名稱,這名字只會在讀取檢驗階段發揮作用。
Save() 和 Load() 若失敗,不會變動原本的資料,並回傳 false,為了簡明並沒寫上檢查的程式碼,以下是執行結果:
永續儲存容器
MemStorage 和 TxtStorage 的差異在於儲存資料的方式,MemStorage 採用的方式是直接對資料複製,TxtStorage 採用的方式是把每一個陣列元素轉換成數值文字,以文子的方式儲存,因此在速度上 MemStorage 較快,但若考量到跨平台則TxtStorage 較佳。
因兩者基本上的差異,TxtStorage 可匯出成 XML 或 TLC 格式,MemStorage 則匯出特殊結構定義的二進位格式,簡稱 MDC(Memory Data Composite)。
適用於 TxtStorage 的匯出入函數:
void TxtStorageExportTLC(std::wostream &wout, const Smart_Ptr<TxtStorage> &TxtStorage_Ptr);
Smart_Ptr<TxtStorage> TxtStorageImportTLC(std::wistream &win, ISpirit *spirit = Spirit_Easy);
void TxtStorageExportXML(std::wostream &wout, const Smart_Ptr<TxtStorage> &TxtStorage_Ptr);
Smart_Ptr<TxtStorage> TxtStorageImportXML(std::wistream &win, ISpirit *spirit = Spirit_Easy);
適用於 MemStorage 的匯出入函數:
void MemStorageExportMDC(std::ostream &out, const Smart_Ptr<MemStorage> &MemStorage_Ptr);
Smart_Ptr<MemStorage> MemStorageImportMDC(std::istream &MDC, ISpirit *spirit = Spirit_Easy);
範例四
以下修改了範例三加入了 TLC 格式的匯出匯入:
int main(int argc, char* argv[])
{
setlocale(LC_CTYPE, ""); // 指定系統現正使用中的地區語言做轉換
Smart_Ptr<IcxxlStorage> Storage_Ptr(new TxtStorage); // TxtStorage 是以文字來儲存資料
Smart_Ptr<C> C_Ptr(new C());
Storage_Ptr->Save(C_Ptr,L"C Backup");
// 匯出 TLC
wostringstream woss;
TxtStorageExportTLC(woss, Smart_Cast<TxtStorage>(Storage_Ptr));
// 由 TLC 產生一個新的 TxtStorage
wistringstream wiss(woss.str());
Smart_Ptr<TxtStorage> TxtStorage_Ptr = TxtStorageImportTLC(wiss);
cout << "原始的屬性值:" << endl;
C_Ptr->Show();
cout << endl;
C_Ptr->Set(12.2,34.4);
C_Ptr->SetStr(L"改變成不同長度的字串");
cout << "改變後的屬性值:" << endl;
C_Ptr->Show();
cout << endl;
TxtStorage_Ptr->Load(C_Ptr,L"C Backup");
cout << "由 TLC 恢復後的屬性值:" << endl;
C_Ptr->Show();
cout << endl;
cout << "TLC 的內容:";
wcout << woss.str() << endl;
cout << endl << endl;
return 0;
}
#include <sstream>
#include <iostream>
#include <locale>
#include <PRESERVE.HPP>
#include <TXTSTORAGE.HPP>
#include <MEMSTORAGE.HPP>
using namespace std;
using namespace CxxlMan;
class A:virtual public cxxlPreserve
{
const char *Str;
protected:
virtual bool cxxlFASTCALL Ref(Filter *F)
{
if(F != NULL && cxxlPreserve::Ref(F))
{
LoadFilter *pLoadFilter = dynamic_cast<LoadFilter*>(F);
if(pLoadFilter == NULL) // 表示 F 是 SaveFilter
{
F->Ref((char *)Str,strlen(Str)+1,L"Str");
}
else // 表示 F 是 LoadFilter
{
return
pLoadFilter->LoadRef(&Str,L"Str");
}
}
else
return false;
return true;
}
public:
A(ISpirit *spirit = Spirit_Easy)
:cxxlObject(spirit)
{
Str = new char[strlen("HI, 大家好")+1];
strcpy((char *)Str,"HI, 大家好");
}
virtual ~A()
{
delete [] Str;
}
void Set(const char *s)
{
csPreserve.Enter();
delete [] Str;
Str = new char[strlen(s)+1];
strcpy((char *)Str,s);
csPreserve.Leave();
}
void Show()
{
csPreserve.Enter();
cout << Str << endl;
csPreserve.Leave();
}
};
int main(int argc, char* argv[])
{
setlocale(LC_CTYPE, ""); // 指定系統現正使用中的地區語言做轉換
Smart_Ptr <A> A_Ptr = new A;
Smart_Ptr<IcxxlStorage> Storage_Ptr(new TxtStorage);
cout << "A::Str 原始內容:";
A_Ptr->Show();
Storage_Ptr->Save(A_Ptr,L"A");
A_Ptr->Set("你好");
cout << "A::Str 永存之後改變後的內容:";
A_Ptr->Show();
cout << "A::Str 從永存取回的內容:";
Storage_Ptr->Load(A_Ptr,L"A");
A_Ptr->Show();
return 0;
}
PSmart_Set
若要包裹不限個數的 cxxlPreserve 子物件,雖然使用容器(如 cxxlList),也可以辦到,但沒法自動處理永續儲存,PSmart_Set 提供 Add()、Delete()、Destroy() 三個加入移除功能,以及 GetList() 以取得列表,以下延續自自範例一
範例六
class D:virtual public cxxlPreserve
{
PSmart_Set<A> m_A_Set;
virtual bool cxxlFASTCALL Ref(Filter *F)
{
if(F != NULL && cxxlPreserve::Ref(F))
{
return true;
}
else
return false;
}
public:
D(ISpirit *spirit = Spirit_Easy)
:cxxlObject(spirit),
m_A_Set(this,L"A_Set")
{
}
virtual ~D()
{
}
void Add(const Smart_Ptr<A> &A_Arg)
{
csPreserve.Enter();
m_A_Set.Add(A_Arg);
csPreserve.Leave();
}
void Clear()
{
csPreserve.Enter();
m_A_Set.Destroy();
csPreserve.Leave();
}
void Delete(const Smart_Ptr<A> &A_Arg)
{
csPreserve.Enter();
m_A_Set.Delete(A_Arg);
csPreserve.Leave();
}
void Show()
{
csPreserve.Enter();
Smart_Ptr<cxxlList<A> > A_List = m_A_Set.GetList();
cout << "有 " << A_List->GetCount() << " 個 A 子物件:" << endl << endl;
A_List->ResetPT(toHead);
int n = 1;
for(Smart_Ptr<A> A_Ptr = (*A_List)++;
n <= A_List->GetCount();
A_Ptr = (*A_List)++, ++n )
{
cout << "#" << n << endl;
if(A_Ptr.isNULL() == false)
cout << "\tHP = " << A_Ptr->GetHP() << endl
<< "\tMP = " << A_Ptr->GetMP() << endl << endl;
}
csPreserve.Leave();
}
};
int main(int argc, char* argv[])
{
setlocale(LC_CTYPE, ""); // 指定系統現正使用中的地區語言做轉換
Smart_Ptr<D> D_Ptr = new D;
Smart_Ptr<A> A1_Ptr = new A;
A1_Ptr->Set(100,200);
D_Ptr->Add(A1_Ptr);
Smart_Ptr<A> A2_Ptr = new A;
A2_Ptr->Set(150,250);
D_Ptr->Add(A2_Ptr);
D_Ptr->Add((A*)NULL); // 加入一個 NULL
D_Ptr->Show();
// 匯出 TLC
Smart_Ptr<IcxxlStorage> Storage_Ptr = new TxtStorage;
Storage_Ptr->Save(D_Ptr,L"D Backup");
wostringstream wsout;
TxtStorageExportTLC(wsout, Smart_Cast<TxtStorage>(Storage_Ptr));
cout << "TLC 的內容:";
wcout << wsout.str() << endl;
return 0;
}
MDC 格式簡介
這是 MemStorage 的匯出入用的資料格式,在使用上不須也不該去了解它,這裡只是做個記錄。
以下是示意性的表示法,實際的資料是緊密的不會有縮排空格。
{節點名稱位元組數 節點名稱
-SetCount字串位元組數 SetCount unsigned long 形別位元組數 同節點群組個數
{節點名稱位元組數 節點名稱
-項目名稱位元組數 項目名稱 資料位元組數 資料
-項目名稱位元組數 項目名稱 資料位元組數 資料
}
-項目名稱位元組數 項目名稱 資料位元組數 資料
-項目名稱位元組數 項目名稱 資料位元組數 資料
}
{ } - 這三個是識別標籤,佔用一個位元組
位元組數 用來表達隨後資料的數目,佔用 sizeof(unsigned long)