在閱讀 TAF 原始碼時,發現一段將基類指標強制轉為衍生類指標的代碼:
ServantProxy* pServantProxy = getServantProxy(...); proxy = (typename T::element_type*)(pServantProxy); // T 為衍生類智慧指標
乍看之下極其危險:若衍生類新增資料成員,基類物件記憶體佈局較小,透過衍生類指標存取新成員將導致越界訪問。但實測發現程式正常運行,原因在於衍生類未新增任何資料成員,僅新增成員函數。
透過簡單實驗驗證:
#include <iostream>
using namespace std;
class A { public: A(){} ~A(){} };
class B { public: B(){} virtual ~B(){} }; // 有虛函數,含 vptr
class C { public: C() {} ~C() {} int add(int a, int b) { return a+b; } };
int main() {
cout << "Size(A) = " << sizeof(A) << endl; // 1 (空類別最小 1 byte)
cout << "Size(B) = " << sizeof(B) << endl; // 8 (64位系統下 vptr 8 bytes)
cout << "Size(C) = " << sizeof(C) << endl; // 1 (非虛函數不佔空間)
return 0;
}
結果證明: 只有虛函數會引入虛函數表指標(vptr),佔用對象記憶體;普通成員函數(含非虛函數)完全不佔用對象存儲空間。同一類別的所有對象共享同一份函數代碼,代碼儲存在代碼段,與對象實例無關。
成員函數實際上是「帶隱含 `this` 指標的普通函數」。調用 `obj.func()` 時,編譯器隱式傳遞 `&obj` 作為 `this` 參數。因此:
此特性支撐了「介面類(純虛類)+ 實作類」的設計模式:
class IService { // 介面類,僅含純虛函數
public:
virtual void doWork() = 0;
virtual ~IService() {}
};
class ServiceImpl : public IService { // 實作類,可自由增刪非虛函數
public:
void doWork() override { /* ... */ }
void helper() { /* 僅內部使用,不影響記憶體佈局 */ }
};
只要不新增資料成員,`IService*` 安全轉為 `ServiceImpl*` 並調用 `helper()` 完全合法,這正是「物理上不依賴、邏輯上屬於」的生動體現。
理解此機制,能讓你在處理多型、轉型、二進位介面設計時更加從容不迫。