永續儲存

提供執行時期物件屬性資料的保存和恢復機制,這機制嚴格要求物件須是 cxxlPreserve 的延伸類別,而保存的容器須是 IcxxlStorage 的延伸類別,目前提供的保存容器實作有 MemStorage 和 TxtStorage 兩種。

成為 cxxlPreserve 元件須做三件事:

  1. 須實作 cxxlPreserve 宣告的 Ref(Filter *F) 永續存取虛擬函數。
  2. 須用 PSmart_Ptr 或 PSmart_Set 連結 cxxlPreserve 子物件(包括 NULL 物件)。
  3. 同步控制須使用 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() 所要求的名稱和存取無關,甚至可用空字串,或相同的名稱,這名字只會在讀取檢驗階段發揮作用。 

範例三

以下是一個簡單的測試範例
int main(int argc, char* argv[])
{  
  setlocale(LC_CTYPE, "");            // 指定系統現正使用中的地區語言做轉換
  // Smart_Ptr<IcxxlStorage> Storage_Ptr(new TxtStorage);
  Smart_Ptr<IcxxlStorage> Storage_Ptr(new MemStorage);
  Smart_Ptr<C> C_Ptr(new C());
  cout << "原始的屬性值:" << endl;
  C_Ptr->Show();
  cout << endl;
  Storage_Ptr->Save(C_Ptr,L"C Backup");
  C_Ptr->Set(12.2,34.4);
  C_Ptr->SetStr(L"改變成不同長度的字串");
  cout << "改變後的屬性值:" << endl;
  C_Ptr->Show();
  cout << endl;
  Storage_Ptr->Load(C_Ptr,L"C Backup");
  cout << "恢復後的屬性值:" << endl;
  C_Ptr->Show();
  cout << endl << endl;
  return 0;
}
 
 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;
} 
 
 
 執行結果:
 
 
 

讀取可變動永存資料

class LoadFilter 提供以下這個方法,可以用比較簡單的方式讀取會變動的永存資料:

bool LoadRef(T **v, const UNICODE_String &Name) = 0; (T 是一般型別)

這函數可同時用於檢查和實際讀取兩個階段,在檢查階段只檢查名稱是否正確,若所有檢查都回報 true 才會在讀取階段實際做讀取處理,否則讀取階段 Ref() 的 Filter *F 引數得到的會是 NULL。在讀取階段會先用 delete [] *v 刪除原本的資料,再用讀取到的永存資料替換上去。

這個方法可以代替大部份 LoadChk() 的須求,但使用上是有條件的,處理對象須是一個指標,且是可以變動的,且在替換當時和之後都不能有不良的影響,所以我曾一度想讓 MB_String 和 UNICODE_String 也能由這功能來永續儲存,但因這兩者早被定調為唯讀共用,因而作罷。以下用一個簡單的例子介紹用法:

 

範例五

 
#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)

 
 

增修後記

 r048 PSmart_Set 內部所用的容器,由原先的 cxxlAvl_Tree 改為 SList,讓放入的順序即為排列的順序
 r050PSmart_Set 增加 GetCount() 成員函數


 
  
引入檔

MEMSTORAGE.HPP

TXTSTORAGE.HPP

 

程式庫

cxxlpreserve

 

 
 
 
 
Comments