就某些類而言,當在程序中第一次使用時,最好能有一個初始化過程;當程序不再需要時,也最好能做一些收尾工作,這些都是非常好的類設計習慣。
引出問題
如果有這樣一種情況,某種類型的每個實例都必須有其唯一的ID,比如說某種交易類型,這些ID可用於在處理過程中追蹤每筆交易,或之後用於審計員查看數據文件;為討論方便,此處的ID為從0起始的有符號整型數。
如果把一個nextID值保存在內存中,並在每個新實例構造時,把它遞增1,這無疑是一個不錯的想法,但是,為使在程序連續的執行過程中保持ID值的唯一,就需要在每次程序結束時保存此值,並在下次程序開始運行時恢復這個值,但在標准C++中,是沒辦法來達到這個目的的,實際上,使用標准CLI庫也同樣沒辦法完成。然而,在CLI的.NET實現中有幾個擴展庫,它們卻可以完成這個任務。
問題重現
這回又用到了Point類,因為帶有唯一ID的點很適合此主題。例1中的程序輸出在代碼之後:
例1:
using namespace System;
Point F(Point p) {
return p;
}
int main()
{
/*1*/ Point::TraceID = true;
/*2*/ Point^ hp1 = gcnew Point;
Console::WriteLine("hp1: {0}", hp1);
/*3*/ hp1->Move(6,7);
Console::WriteLine("hp1: {0}", hp1);
/*4*/ Point^ hp2 = gcnew Point(3,4);
Console::WriteLine("hp2: {0}", hp2);
/*5*/ Point p1, p2(-1,-2);
Console::WriteLine("p1: {0}, p2: {1}", %p1, %p2);
/*6*/ p1 = F(p2);
Console::WriteLine("p1: {0}", %p1);
}
輸出:
hp1: [0](0,0)
hp1: [0](6,7)
hp2: [1](3,4)
p1: [2](0,0), p2: [3](-1,-2)
p1: [2](-1,-2)
在程序開始運行時,從一個文本文件中讀取下一個可用的ID值,並用它來初始化一個Point類中的私有靜態(private static)字段。最開始,這個文件包含的值為零。
基於公共靜態布爾屬性TraceID的值,Point中ToString函數生成的字符串可有選擇地包含Point的ID,並以 [id] 的形式作為一個前綴。如果此屬性值為true,就包含ID前綴;否則,就不包含。默認情況下,這個屬性值被設為false,因此,在標號1中我們把它設為true。
在標號2中,使用默認構造函數為Point分配了內存空間,並顯示它的ID為0及值為(0,0)。在標號3中,通過Move函數修改了Point的x與y坐標值,但這不會修改Point的ID,畢竟,它仍是同一個實例--只不過用了不同的值。接著,在標號4中,使用了接受兩個參數的構造函數為另一個Point分配了內存空間,並顯示它的ID為1及值為(3,4)。
在標號5中創建了兩個基於堆棧的實例,並顯示出它們的ID及值。在第三個及第四個Point創建時,它們的ID分別為2和3。
在標號6中,p1被賦於了一個新值,然而,p1仍是它之前的同一個Point,所以它的ID沒有改變。
第二次運行程序時,輸出如下:
hp1: [6](0,0)
hp1: [6](6,7)
hp2: [7](3,4)
p1: [8](0,0), p2: [9](-1,-2)
p1: [8](-1,-2)
如上所示,4個新實例都被賦於了連續的ID值,且與第一次執行時截然不同,但是,還缺少ID 4和5。請留意標號6及函數F的定義,Point參數是傳值到此函數的,而一個Point也是通過值返回的。同樣地,這兩者都會調用到復制構造函數,而其則"忠實"地創建了一個新實例,且每個新實例都有一個唯一的ID。因此,當p2通過值傳遞時,會創建一個ID為4的臨時Point,緊接著,當副本通過值返回時,又會創建一個ID為5的副本,而兩個副本都是可丟棄的。當程序結束時,寫入到文件中下一個可用的ID為6,而在程序下次運行時,這就是第一個Point在分配空間時將用到的ID。