client:
1.listen server 的 packet 以做相對應的處理
2.將 user 的動作(與方塊相關的,如消去,旋轉,落下…等等,傳送到 server)
僅把動作送出去,對圖形的處理仍由 client 處理
3.與方塊相關的流程,圖形處理(自行產生下一個方塊)
server:
1.listen client 的 packet,轉送到其他 client
流程:
說明:
1.Server為一模組,只做 Listen, 然後轉送,為以後可能有多遊戲共用,所以支援 thread;日後 Server 端則獨立,除非有bug,否則不變動.
使用者的 USE CASE 敘述
1.選擇我要連線
遊戲嘗試向Server發出訊息
如果得到成功的回應,則開始遊戲
遊戲會亂數產生方塊,並且一直等待使用者的反應(旋轉,落下,移動)
在方塊產生後,方塊會一次落下一格,直到碰到障礙物為止
接著會畫出來
並且送訊息給Server
當方塊落下後,遊戲判斷是否可以消去方塊
若消去,畫出消去,並送訊息給Server
並且產生下一個方塊
偶爾會產生特殊的武器,使用者可以依據武器的特性來做出反應
2.選擇當主機
遊戲會檢查Server是否起始,若沒有,則起始 Server 的服務.並送出訊息給Server說要等待其他使用者進入.
有的話就送訊息給Server要建立.
等待Server給的通知.
當有通知後請參考1
Server的部分
起始後,會先建立Listen thread
Listen thread會持續聽取傳來的message,訊息定義如下:
new game: 若訊息是要建立新遊戲,則建立 Send thread, 佇列, 和清單,並開始服務
同時把ip位址放到清單中.
(會傳來ip位址以及建立新遊戲的訊息)
要求連線: 若訊息是要求連線,則把ip位址放到清單中,並且依據清單送出可以開始的訊息到佇列中.
(會傳來ip位址以及要求連線的訊息)
轉送: 若是遊戲的訊息,則依據傳來的ip位址,和清單比對,不相符的ip位址,則把訊息以及清單的ip位址放到佇列中.
(會傳來ip位址,以及要告訴其他使用者的訊息)
Send thread:
會持續去檢查佇列,如果有需要送出,則把訊息送出.
佇列:
佇列主要是存放訊息,是的,Listen thread會一直將訊息塞到這裡去.
清單:
這張清單主要是存放連線上來的電腦的ip位址.
方塊類別(Tile)
狀態:9×9 matrix
旋轉: rotate
武器(寶物)類別(Weapon)
佔掉的位置為9×9
以圖形表示
會有發射,爆炸,壓掉,亂數消去的動作
遊戲類別(Game,應該也可說是Form本身)
控制遊戲主要流程
Server類別(Server)
thread list:紀錄產生後的thread的id
佇列 list: 這個list裡面就是放佇列
清單 list: 這個list裡面就是放清單
listen thread id
訊息類別(Message)
Body 訊息內文,基本上為一字串
1~10表示Game種類
11表示訊息種類
2~15表示為傳送來的ip位址
16~以後表示是訊息本身內容,基本上Server並不認識這段訊息,只是忠實的送出
CVS 簡易說明
我們先假設 192.168.0.1 有安裝 cvs server
並且有三個專案在上面
怎麼用??
0. 確定自己電腦上有安裝 cvs
一般來說,如果是 Red Hat 或 Mandrake,那麼預設會有安裝
如果是其他的,我就不知道了
請到 www.cvsroot.org 去下載套件並安裝之
1. 設定
#當然你如果習慣用 vi, 這邊的 EDITOR 就設為 vi 的路徑
export EDITOR=/usr/bin/vim
#此處的 username, 要替換為自己的 username
export CVSROOT=:pserver:username@192.168.0.1:/home/cvsroot
export CVSREAD=yes
你可以把這些敘述放到 /etc/profile 或 /root/.bash_profile 裡面
免得以後每次用的時候還要再打一次
2. login
輸入
cvs lgin
cvs 會要求你輸入密碼,此密碼就是你在 192.168.0.1 上的密碼
3. checkout 將專案下載到你的電腦上
所以你可以打
cvs checkout clockd
cvs checkout helloworld
cvs checkout qt
把專案下載到自己的電腦上
4.
做完 checkout 後,你可以看到自己的電腦上出現了專案
先輸入 cvs edit xxxx
然後就可以做編輯了,你可以試著亂改, 加上一些註解或無聊的程式碼
做完修改後,當然要放回 server 囉
你可以用
cvs commit [檔案名稱]
指令來更新回 server, 更新之前, cvs 會打開 editor 讓你輸入一些文字,為這次的修改作一些說明
對了,如果別人在你修改之前修改了這個檔案,那麼 cvs 會自動將檔案作合併,讓你再去做編輯,請務必玩一玩
5.
如果要看目前的狀況,就打
cvs status [檔案名稱]
cvs diff [檔案名稱]
cvs history [檔案名稱]
來查看各種狀況
6.
不想要這個檔案加入版本管理的話,得先刪除該檔案後,再使用
cvs remove [檔案名稱]
來移除
7.若要登出 cvs server,則輸入 cvs logout 即可
cvsweb.cgi
前幾天, 友人找到一個不錯的 cvs web 介面,粉漂亮
是用 Perl 寫的CGI,於是我試著把他裝在 cvs server 上面~
一般的安裝方法,就是找到你的 httpd 伺服器,然後放在 cgi-bin/ 下面
以 redhat 為例,就是放在 /var/www/cgi-bin/ 下面
並且把執行的屬性改為執行~執行的擁有者改為適當的擁有者
但是在安裝以後
發現友人給我的檔案無法使用
於是我 follow 該檔裡面的網址前去下載~~
網址如下:
http://www.freebsd.org/cgi/cvsweb.cgi/www/en/cgi/cvsweb.cgi/
下載之後
發現仍然無法使用
他告訴我還需要
cgi-style.pl
我的老天,網海茫茫,到哪裡去找??
後來突然發現
http://www.freebsd.org/cgi/cvsweb.cgi/www/en/cgi/
這邊就可以看到 cgi-style.pl
下載之後
又發現我沒有 cvsweb.conf
於是再次從
http://www.freebsd.org/cgi/cvsweb.cgi/www/en/cgi/
找到 cvsweb.conf
這樣子,檔案就全部到齊了
請用編輯器打開 cvsweb.cgi, 找到以下這行
for (“$mydir/cvsweb.conf”, ‘/usr/local/etc/cvsweb/cvsweb.conf’) {
加入 /etc/cvsweb.conf,於是就變成
for (“$mydir/cvsweb.conf”, ‘/usr/local/etc/cvsweb/cvsweb.conf’,’/etc/cvsweb.conf’) {
然後再用編輯器打開 cvsweb.conf, 找到以下這行
@CVSrepositories = (
…….
);
這個 Perl 陣列裡面就是你的 CVS 存放的位置也就是 CVSROOT 所指的位置
請修改之以符合你的環境.
假設,你的 CVSROOT 是 /home/cvsroot/, 裡面有一個 respository 為 libs
那麼裡面就應該是
‘libs’ => [‘libs’, ‘/home/cvsroot’],
緊接著@CVSrepositories 下面,你會看到 %MIRRORS
如果你的 CVS Server 有 Mirror 的話
也請修改,否則的話,就把他清成空的,像是這樣~
%MIRRORS = (
);
修改之後,再打開瀏覽器去執行,果然看到 cvsweb 漂漂的畫面
一切就大功告成了~~
以xcopy作備份
@echo off
rem
rem 先取得遠端信任,並建立網路磁碟機
rem
net use z: \\bserver\backup password /user:username
rem
rem 進行複製
rem /e 連子目錄也一起複製,包括空目錄
rem /v 複製完成,驗證
rem /h 連隱藏檔跟系統檔也一起複製
rem /z 在可重新開始的模式中複製網路檔案。
rem /y 遇到重複,不詢問,直接複製
rem /d:m-d-y 可加上日期,表示 y 年 m 月 d 日以後的檔案才複製
rem /i 假設目的端指定的名稱不存在,則自動建立為目錄
rem
rem 備份列表可以在下面繼續新增
rem
xcopy /e /v /h /z /y /i d:\snitzforums z:\snitzforums
xcopy /e /v /h /z /y /i d:\public z:\public
J2SE in linux安裝導引
首先到 Sun(http://java.sun.com) 網站下載最新版的 j2se for linux.
假設你下載的是最新版的 1.4 版,檔名是 j2sdk-1_4_0-fcs-linux-i386.rpm.bin
並放到 /download 資料夾中
$chmod +x j2sdk-1_4_0-fcs-linux-i386.rpm.bin
$./j2sdk-1_4_0-fcs-linux-ie86.rpm.bin
此時會詢問你是否同意其版權,選擇 yes 以後,將會解出 j2sdk-1_4_0-fcs-linux-ie86.rpm
$rpm -ivh j2sdk-1_4_0-fcs-linux-i386.rpm
這樣子就安裝完成了,預設會安裝到 /usr/java/j2sdk1.4.0/ 下,接著要做一些設定
請用你順手的文書編輯器,編輯 /etc/profile
加入這幾行
# Java
export CLASSPATH=.:/usr/java/j2sdk1.4.0/lib
PATH=”$PATH:/usr/java/j2sdk1.4.0/bin”
把 CLASSPATH 和 PATH 都指向 /usr/java/j2sdk1.4.0/ 下~
登出再重新登入之後,應該就可以使用 j2se 了
讓我們編寫一個簡單的 java 程式來實驗看看,程式碼如下
class HelloLinux { public static void main(String[] args) { System.out.println("Hello Linux!!"); } }
並命名為 HelloLinux.java
用 javac HelloLinux.java 編譯之.
用 java HelloLinux 執行之,注意,此處不可以用 java HelloLinux.class, 否則會發生錯誤!!這也是初學者常有的錯誤之一~
運行順利的話,你應該會看到
Hello Linux!!
Yes, Hello Linux
絕佳的學習指引:
http://java.sun.com/docs/books/tutorial/
碁峰有把這部分出書~現在已經出到 3rd Edition 了~不錯!
Freebsd 4.5初探心得
- bsd 的磁碟分割與 linux 大不同,bsd 是先分割出一個分割區之後,再在該分割區上分出掛載點及 swap 等.
- 沒有 ifup, ifdown, /proc/net/dev, 因此只能利用 ifconfig rl0 up, ifconfig rl0 down,
ifconfig -l 來代替. - 沒有 /proc/net/dev,因此無法利用他來看網路卡的 rx bytes, tx bytes, 但可以利用 netstat -I rl0
-b 來看 - df -h 和 linux 的大致相同,但 Use% 換為 capacity
- 沒有 /etc/sysconfig/network-scripts/ifcfg-eth0, 統一存放在 /etc/rc.conf 裡面. 如果是rl0,
那麼該檔裡面會有一行 ifconfig_rl0="…….". 當 rl0 欲設為 dhcp,那麼 ifconfig_rl0="DHCP"即可.若欲設為static,
那麼就要設 ifconfig_rl0="inet 192.168.0.1 netmask 255.255.255.0" - dhcp 無法在 rc.conf 中設定後,再用 ifconfig rl0 up 取得,而須利用 dhclient rl0 來取得
- 可用 /stand/sysinstall 這個管理工具來更動 rc.conf 中的內容
套件粉多,但預設都不會裝進去~~我想有可能是我不會選吧~
總之都是我後來自行安裝進去的.
安裝一套的速度頗快,不用一個小時就裝完了,很類似 debian, 都是先把 kernel 的東西裝進去之後,才讓你選套件.我想這就是快的原因吧.
Building Solutions for VB In Windows 2000
講師:羅慧真
筆記者:ellery
檢查需要的元件是否安裝
No -> On demand installtion
Yes -> 檢查元件是否適當安裝,若miss,則 On demand repairing.
Install Sheild for Windows 2000 or Wise 2000
本身不提供 Module 檔,所以要自行將 Modules 檔複製進去.
Visual Installer 不需要的原因是因為, Microsoft 已經將 VB 所需的 Modules 放進去了.
Package 若以 Publish 發行,則 user 要在 Add/Remove 安裝.
若 Assign -> user, 該 user login 進去後就會開始安裝
若 Assign -> computer, 只要該電腦一開機進入就會開始安裝
How to implement side by side?
在欲保留原 DLL 的 AP 路徑下,建立一文字檔,更名為 ap.exe.local, 如此一來就會使用到原 DLL.
p.s. only for Windows 2000.
如果環境為 95/98/NT, 可將 CLSID 機瑪下的 InprocServer32 的Default內容的路徑拿掉.
Group Policy 的一些相關設定擋在 \WINNT\SYSVOL\DOMAIN 下面,可利用 Notepad 去查看.
Directory Service
1. Workgroup | |
![]() |
每台PC都必須設定 user account 缺點:分散管理 Security 差,維護困難 一個資源只能同時給 10 個 connection |
2. Windows NT 4.0 Domain | |
![]() |
優點: One user one account Universal resource access centralize administration 缺點: Trust 沒有遞移性,需手動維護 Truest Trust 為 Domain 間相同 Account 共享 Resource. |
3. Windows 2000 | |
![]() |
DC安裝步驟:
- My Computer -> 右鍵 Property -> Network Identification -> Property
-> Advance -> Other, 指定 Domain Name - Network -> 右鍵 Property -> Local area connection -> 右鍵 property
-> TCP/IP 指定 DNS 的 IP, Advance DNS 指定 Domain Name - ipconfig /all 檢查 DNS 及 domain name 是否正確
- DC Promote 決定角色
- Domain 的第一台 PC或第二台
- Root Domain 或 sub domain
- 加入另一 domain tree 為 forest
AD Schema
- regsvr32 schmmgmt.dll
- mmc
- snap-in ad schema
LDAP Name:
1.DN -> Distiguished Name
CN: Common Name +Container +User +Computer Name
OU: Organization Unit +ou +group
DC: Domain Component +Domain Name +Type +Location
Example: CN=User CN=Guest CN=London, ou="Domain Controller", DC=Microsoft, DC=COM uuu.com.tw 即 domain.type.location
以下這一狗票東西,是你可以寫,然後放到 Group Policy 裡面的,在.apm 檔案中(WINNT\INF\SYSTEM.adm)
CATEGORY "Media Player Settings" POLICY "Store with this view" KEYNAME "Software\Microsoft\MediaPlayer\Player\Settings" EXPLAIN "Used to set the initial Media Player view" PART "Select View" DROPDOWNLIST NOSORT VALUENAME "view" ITEM LIST NAME "Compact" Value 0 NAME "Standard" Value 1 NAME "Minimal" Value 2 END PART END POLICY END CATEGORY
Logon.
Queue
筆記:深度探索C++物件模型 第一章
摘要
C++ Object Model
一般來說有三種實作方式:
1.Simple: 一個Object內有一個 slot table, table 中各 slot 指向一個 member.
2.Table: 把 member 和 function 抽出來為 兩個 table, 物件內含一指標指向這兩個 table, 而 function
table 又指向各 function.
3.目前一般常見 C++ Compiler 所採用的物件模型, 則是將 member 放在物件中,物件內含一指標指向 function table,
而 static member 則另外存放於 heap 中.
繼承
早期,直接把 base class member 含括到 derived class, 較快; 但若 base class 變更,會造成 derived
class 需重新 compile.
2.0 規格以後,為有關聯的 virtual base class 加上指標:
- 導入 virtual base class table
- 擴充現有的 virtual table
簡單的說就是在衍生類別中加上指標指回 base class 的 member 和 function.
虛擬繼承
亦可指定 virtual, 如 iostream
class istream: virtual public ios {...}; class ostream:virtual public ios {...};
此情況下, base class 不管在繼承串列中被衍生幾次,均只存在一個實體,上例中, ios 將只存在一個.
class 和 struct
class 和 struct 幾乎相同,但仍然請依照需要乖乖使用.
多型
只有透過 pointer 或 reference 的間接處理,才支援多型.
筆記:深度探索C++物件模型 第四章
Function
語意學 ( The Semantics of Function )
摘要:
nonstatic member function
nonstatic member function 實際上會被內化為 nonmember 的形式,步驟如下:
- 改寫函式的 signature 以安插一個額外的參數,用以提供一個存取管道,使 class object 得以將此函式喚起.額外參數就是 this.
- 將對 "nonstatic data member 的存取動作" 改經由 this 指標存取.
- 改寫成一個外部函式,將函式名稱經過 "mangling" 處理,以成為一個獨一無二的語彙.
名稱的特殊處理 (Name mangling)
一般而言, member 的名稱後面會被加上 class 名稱. 若你宣告 extern "C", 就會壓抑 nonmember
functions 的 "mangling" 效果. 此 mangling 的動作, 各家 compiler 實作方式不同.
virtual member functions
若 ptr->normalize();
則會被轉化為
(*ptr->vptr[1])(ptr);
其中:
- vptr 表示由 compiler 產生的指標,指向 virtual table, 其名稱也會被 "mangled", 因為在一個複雜的衍生體系中,可能存在多個
vptrs. - 1 是 virtual tabe slot 的索引值.
- 第二個 ptr 表示 this 指標.
若宣告為 inline, 則會被 compiler 當作一般 nonstatic member function 一樣地決議,提供極大的效率利益.
static member function
一般建議把 static data member 宣告為 nonpublic, 並提供一個或多個 member functions 來存取之.
主要特性: 沒有 this 指標.
次要特性: 他不能直接存取其 class 中的 nonstatic members; 他不能被宣告為 const, volatile, 或 virtual;
他不需要經由 class object 才被喚起.
如果取一個 static member function 的位址, 將獲得其在記憶體中的位置,其位址的型別並不是一個 "指向 class member
function 的指標", 而是一個 "nonmember 函式指標".即
&Point3D::object_count();
會得到
unsigned int (*)();
而非
unsigned int (Point3D:*)();
差不多等同於 nonmember function.
p.s. object_count 原型宣告為
unsigned int Point3D:: object_count() { return _object_count; }
virtual member function(單一繼承)
單一繼承一般是在每個多型的 class object 身上增加 2 個 member:
- 一個字串或數字,表示 class 型別.
- 一個指標指向某表格,表格中持有程式的 virtual functions 的執行時期位址.為了找到函式位址,每個 virtual function
被指派一個表格索引值.
這些工作都由 compiler 完成. 執行時期要做的只是在特定的 virtual table slot 中啟動 virtual function.
圖解. 若 Point3D 繼承 Point2D 繼承 Point, 那麼個別的 virtual table 就可能是
於是當
Point *ptr; ptr=new Point3D(); ptr->z();
compiler 可以把該呼叫轉化為
(*ptr->vptr[4])(ptr);
多重繼承
在多重繼承中支援 virtual function,其複雜度圍繞在第二個及後繼的 base class 上,以及"必須在執行時期調整 this
指標"上.
即後繼的 class 會有多個 virtual table.
將後繼的物件位址指定給一個 base1 指標或 base2 指標時, virtual table 就要視指標的型態作切換,以免呼叫到錯誤的函數.
效率若依照原始 c++ 模型,會變的不好,但這方面各家 compiler 會利用 thunk 或 address points 策略來改善.
虛擬繼承下的 virtual functions
實作上,同樣要調整 this 指標,很複雜,效率也不一定較好,建議不要在一個 virtual base class 中宣告 nonstatic data
members.
函式的效能
inline > (nonmember friend=static member=nonstatic member) > virtual
member > virtual member(多重繼承) > virtual member(虛擬繼承)
virtual member 在層數越多的狀況下,其執行時間也成正比增加.
Point-to-Member functions
double (Point::*pmf)(); double (Point::*coord)()=&Point::x; //初始 coord=&Point::y; //或是這樣初始
於是可以
(origin.*coord)();
或
(ptr->*coord)();
這樣用.
實際上則會轉化為
(coord)(&origin);
和
(coord)(ptr);
支援"指向 virtual member functions"的指標
考慮如下片段(假設 z 為 virtual function)
float (Point::*pmf)()=&Point::z; Point *ptr=new Point3D; ptr->z(); //ok (ptr->*pmf)(); //仍然 ok
compiler 實作上,必須定義 pmf, 使他能持有兩種數值,並且其數值能區分其意義.
多重繼承的狀況:
stroustrup 利用 union 來處理
struct __mptr { int delta; int index; //處理 virtual table 索引,不指時為 -1 union { ptrfunc faddr; int v_offset; //處理 nonvirtual member function }; };
於是
(ptr->*pmf)();
會變成
(pmf.index<0)? (*pmf.faddr)(ptr): (*ptr->vptr[pmf.index](ptr));
Microsoft 以 vcall thunk 來作檢查,避免浪費檢查的時間,但副作用是,當傳遞一個不變值的指標給 member function 時,需要產生暫時性的物件.
效率:同樣地,不牽涉到"虛擬"+"多重"情況的,效率較佳.
inline function
一般處理時,有兩個階段:
- 分析函式,若因某些問題(複雜度過高,建構問題…等)被判斷不可 inline, 則會轉為 static 函式,並在被編譯模組中產生對應的函式定義.
- 真正的 inline function 擴展動作,是在呼叫的那一點上,這會帶來參數的求值動作及暫時性物件的管理.
通常需進入 assembler 中,才能得知是否真實現了 inline
形式參數擴展的情況大致如下:
inline int min(int i, int j) { return i<j?i:j; } int main() { int minval; int val1=1024,val2=2048; minval=min(val1,val2); //minval=val1<val2?val1:val2; minval=min(1024,2048); //minval=1024; minval=min(foo(),bar()+1); //int t1,t2; //minval=(t1=foo()),(t2=bar()+1),t1<t2?t1:t2;
區域變數的情況
若
inline int min(int i, int j) { int minval=i<j?i:j; return minval; } int local_var; int minval; ... minval=min(val1,val2);
則可能會代換為
int local_var; int minval; int __min_lv_minval; minval=(__min_lv_minval=val1<val2?val1:val2),__min_ln_minval;
inline 函式中的區域變數再加上有副作用的參數,可能會導致大量暫時性物件的產生.並且使得程式大小暴增.避免過於複雜的 inline 函式,以免 compiler
無法擴展開來.
筆記:深度探索C++物件模型 第三章
The Semantics of Data
摘要
static data member
static data member 永遠在 global data segment, 不影響 class object 的大小.
物件的大小
class object 的大小有可能因為 compiler 實作物件模型的方式不同而有不同,原因:
- compiler自動加上額外 data member, 以支援某些語言特性.
- alignment 邊界調整需要(比如: char 為配合機器特性而以 long 存放)
雖然目前 C++ compiler 已經非常進步,但早期會有兩種程式寫作防禦方格,以防止資料繫結錯誤.
- 把 data member 寫在 class 的開頭.
- inline function 移到 class 宣告之後,而不在宣告區裡面實作, 如:
extern int x; class Point3D { private: float x,y,z; public: float X() const {return x;} //到底傳回哪個 x 呢? float getX() const; //早期若不這樣寫,會造成資料繫結到上面那個 x }; inline float Point3D:: getX() const { return x; }
data member 的佈局
data member 實際的存放(佈局)
注意:各 data member 再實際存放時不一定連續.
如:
class Point3D { float x; float y; float z; };
x,y,z 不一定是連續的,有可能為了要補 alignment 或因compiler 的調整為 y,x,z, 主要原因是 C++ Standard 對此採放任態度.一般而言,仍是連續的.
data member 的存取
當 member 被宣告為 static 時,實際上和一般變數存取一樣,因為 static member 存放在 class 之外,不需要再透過 class
去存取. 若不是宣告為 static, 事實上,都會透過一個隱含的 class object (this) 完成,也就是類似這樣
Point3D Point3D:translate( Point3D* this, const Point3D &pt) { this->x+=pt.x; this->y+=pt.y; this->z+=pt.z; }
若在程式中對 data member 做存取,如:
origin.y=0.0;
那麼實際上將等於
&origin+(&Point3D::y-1)
指向 data member 的指標,其 offset 值總是被加上 1, 這樣子 compiler就能需分出"一個指向 data member
的指標,用以指出 class 的第一個 member"和"一個指向 data member 的指標,未指向任何 member"的情況.(那就是上面為什麼要
-1的原因).
繼承與 data member
在沒有 virtual function 的狀況下,和 struct 相同.原本是獨立不相干的 class 湊成 type / subtype, 並有繼承關係(如
Point2D -> Point3D), 經驗不足的人可能會重複設計一些相同動作的函式,以 constructor 和 operator += 為例,
可以做成 inline. 另外把一個 class 分解為二層或更多層,有可能會為了"表現 class 體系之抽象化"而膨脹所需空間.如:
parent 為兩個 int, child 為一個 char, grandchild 為一個 char, compiler 會為了 alignment
而填補空間.
加上 virtual function 後,將需要導入 vptr, 並在每個函式做 vptr 的處理, 解構時也要把 vptr 抹消,一般而言, vptr
放在 class 的最後面,但 visual c++ 放在最前面,主要是為了繼承的效率問題.
class 若內含一個或多個 virtual base class subobjects, 將會分割為二部分:一個不變區域和一個共享區域,不變區域的資料,不管後繼如何衍化,總擁有固定的
offset(自 object 的起頭算起), 所以可以直接存取,至於共享區域,表現的是 virtual base class subobject, 其位置會因為每次衍生動作而有變化,只能被間接存取,
compiler 會在子類別中安插一些指標,每個指標指向一個 virtual base class.這樣的做法有兩個缺點:
- 每個物件必須針對其每個 virtual base class 背負一個額外指標(空間增加).
- 由於虛擬繼承串鏈的加長,導致間接存取層次增加.(時間增加)
大部分編譯器到今天仍使用"經由拷貝動作取得所有的 nested virtual base class 指標,放到 derived class
object 之中"來解決第二個問題.
第一個問題一般有兩個解法. Microsoft compiler 引入 virtual base class table, 每一個 class object
如果有一個或多個 virtual base class, 就會由 compiler 安插一個 pointer, 指向 virtual base class
table. 第二個方法是在 virtual function table 中放置 virtual base class 的 offset.
指向 data members 的指標
可用以決定 vptr 放在 class 的 begin 或 end, 另一個用途可用來決定 class 中的 access sections.
usage:
printf("&Point3d::x=%p\n",&Point3d::x);
如果 vptr 在物件尾巴,則 offset 為 0
如果 vptr 在物件起頭,則 offset 為 4
但為何在尾巴時,傳回值總是多 1? 意即 1 主要是用以區分"沒有指向任何 data member" 的指標和 "指向第一個
data member" 的指標.
注意,此種額外的間接性會降低"把所有處理都搬移到暫存器中執行"最佳能力.