軟件分發與C++
以源代碼形式分發:
問題1: 每個可執行文件都將包含類庫的代碼, 浪費磁盤空間, 如果用戶同時運行包含該類庫的幾個應用,浪費虛擬內存.
問題 2: 一旦類庫廠商發現了缺陷,沒有任何辦法可以替換部分實現代碼
<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />
動態鏈接與C++
引入庫不包含實際的代碼,由鏈接器產生, 它包含一些引用,指向DLL的文件名和被引出的符號名.
有了引入庫,機器碼在硬盤上只保留一份
C++的可移植性
因爲存在名字改編(name mangling), A編譯器的客戶不能與B編譯器的引入庫成功鏈接.
Extern “C” 是消除名字改編現象的經典技術, 但它不能用於成員函數.
在客戶的鏈接器上做一些文章, 使用DEF(Module Definition File): 它允許引出符號被化名爲不同的引入符號,庫廠商可爲不同的編譯器產生專門定製的引入庫.
A編譯器產生的函數,它所拋出的異常,不能被B編譯器生成的客戶程序捕捉到.
封裝性與C++
C++通過private 和public 關鍵字支持語法上的封裝性, 但沒有定義二進制層次上的封裝性.
這是因爲C++的編譯模型要求客戶的編譯器必須能夠訪問與對象的內存佈局有關的所有信息,這樣才能構造類的實例, 或調用類的非虛成員函數. 這些信息包括對象的私有成員和公共成員的大小與順序.
把接口從實現中分離出來
接口類應該只描述實現者希望客戶知道的底層數據類型的面貌.
使用句柄類作爲接口.
缺點: 每個新增的方法要增加兩個函數調用, 易出錯,且開銷大
抽象基類作爲二進制接口
假設1: 複合類型在運行時的表現形式對於不同的編譯器往往會保持不變
假設2: 所有的編譯器都強制使用同樣的順序傳遞函數參數並且堆棧的清理也按統計表的方式進行.
假設3: 某個給定平臺的C++編譯器都實現了同樣的虛函數調用機制
抽象基類的要求:
方法爲virtual
DLL引出一個全局函數,由它代表客戶調用new, 使用extern “C”
增加一個Delete虛方法, 代替虛析構函數(因爲它在vtal中的位置與編譯器相關)
接口類的虛函數總是通過保存在vtbl中的函數指針被間接調用, 客戶程序不需要在開發時候鏈接這些函數的符號名
運行時多態性
用戶可以動態的裝入DLL, 減少地址空間初始化的工作,而且如果對象實現代碼如果沒有真正被使用的話,DLL就不會被裝入.
允許客戶在不同的實現之間動態地作出選擇
對象擴展性
若追加新的方法: 老客戶得到新對象仍可工作,但新客戶得到老對象不能調用新方法
允許實現類顯露更多的接口:
一個接口繼承另一個相關的接口, 讓實現類繼承多個不相關的接口
客戶利用RTTI確保當前對象支持客戶請求的功能
但RTTI與編譯器相關, 爲此
從每個接口的暴露一個通用的方法dynamic_cast(), 使之完成與dynamic_cast 相同的工作
資源管理
引用計數使用原則:
接口指針被複制時調用DuplicatePointer()
不再使用時調用DestroyPointer()
指針被看作是有獨立生命週期的實體,所以客戶不需要把哪個指針和哪個對象聯繫起來
允許對象自己管理自己的生命週期