用C表達面向對象語言的機制2——顛覆你對方法調用的看法!
源代碼在文末。推薦閱讀本文PDF版,格式更好看。
在上一篇《用C表達面向對象語言的機制——C#版》中,我們獲知了如何用C表達面向對象語言的機制,證明了面向對象語言是對面向過程語言的封裝。今天有幸看到《顛覆你對方法調用的看法!》,於是繼續用C來模擬此文中的代碼,看看“顛覆”的背后是什么。
1. 目標
本文展示用C的union來模擬C#的一些代碼的寫法。
2. 用union代替FieldOffset
例如如下的C#代碼。
Manager class Derived : Base
{
int d = 11;
public new void Print()
{
Console.WriteLine("in derived ({0})", this.d);
}
}
[StructLayout(LayoutKind.Explicit)]
class Manager
{
[FieldOffset(0)]
public Base b = new Base();
[FieldOffset(0)]
public Derived derived;
}
如果想用C來實現類似的使用方式,應該如何寫呢?
1) 用union代替FieldOffset
用C的union代替FieldOffset等屬性。
FrancisYoungBasetypedef struct _FrancisYoungBase
{
// basic info
Metadata * metaInfo;
// fields
int b;
// virtual methods
} FrancisYoungBase;
// type id
static int FrancisYoungBaseTypeId = 11;
// method declarations
// the new method
FrancisYoungBase * NewFrancisYoungBase()
{
// alloc for space
FrancisYoungBase * pResult = (FrancisYoungBase *)malloc(sizeof(FrancisYoungBase));
// initialize basic info
pResult->metaInfo = NewMetadata(
pResult,
FrancisYoungBaseTypeId,
NULL);
// initialize fields
pResult->b = 10;
// initialize virtual methods
// return result
return pResult;
}
void Print4FrancisYoungBase(FrancisYoungBase * pThis)
{
if (pThis == NULL) { return;/* throw exception in C# */ }
printf("in base (%d)\n", pThis->b);
}
FrancisYoungDerivedtypedef struct _FrancisYoungDerived
{
// basic info
Metadata * metaInfo;
// fields
int d;
// virtual methods
} FrancisYoungDerived;
// type id
static int FrancisYoungDerivedTypeId = 12;
// method declarations
void Print4FrancisYoungDerived(FrancisYoungDerived * pThis);
// the new method
FrancisYoungDerived * NewFrancisYoungDerived()
{
// initialize base class
FrancisYoungBase * pBase = NewFrancisYoungBase();
// alloc for space
FrancisYoungDerived * pResult = (FrancisYoungDerived * )malloc(sizeof(FrancisYoungDerived));
// initialize basic info
pResult->metaInfo = NewMetadata(
pResult,
FrancisYoungDerivedTypeId,
pBase->metaInfo);
// initialize fields
pResult->d = 11;
// initialize virtual methods
// return result
return pResult;
}
void Print4FrancisYoungDerived(FrancisYoungDerived * pThis)
{
if (pThis == NULL) { return;/* throw exception in C# */ }
printf("in derived (%d)\n", pThis->d);
}
FrancisYoungManagertypedef struct _FrancisYoungManager
{
Metadata * metaInfo;
union
{
FrancisYoungBase * pBase;
FrancisYoungDerived * pDerived;
} obj;
} FrancisYoungManager;
// type id
static int FrancisYoungManagerTypeId = 13;
// method declarations
// the new method
FrancisYoungManager * NewFrancisYoungManager()
{
// initialize base class
// alloc for space
FrancisYoungManager * pResult = (FrancisYoungManager * )malloc(sizeof(FrancisYoungManager));
// initialize basic info
pResult->metaInfo = NewMetadata(
pResult,
FrancisYoungManagerTypeId,
NULL);
// initialize fields
pResult->obj.pBase = NewFrancisYoungBase();
// initialize virtual methods
// return result
return pResult;
}
就是說,union實現了將父類和子類指針都指向父類的實例的功能。
2) 使用Manager類
C#版的Manager,其典型的使用方式如下。
Manager m = new Manager();
m.derived.Print();// In Derived
對應的C代碼是什么樣的?
FrancisYoungManager * m = NewFrancisYoungManager();
Print4FrancisYoungDerived(m->obj.pDerived);/* In Derived */
與C#版的使用方法異曲同工。
從C版代碼可以看到,編譯器根據pDerived的類型,選擇了調用pDerived的類型中的方法。觀察對C#轉換為C的代碼,可以發現,本質上同名的Print方法,對編譯器而言的代號是不同的,所以編譯器根據pDerived的類型,可以立即確定調用哪個方法。
3. 變為虛函數之后
下面展示將上文中的函數變為虛函數后的處理。
C#代碼如下。
FrancisYoungManagerVirtual [StructLayout(LayoutKind.Explicit)]
class ManagerVirtual
{
[FieldOffset(0)]
public BaseVirtual b = new BaseVirtual();
[FieldOffset(0)]
public DerivedVirtual derived;
}
class BaseVirtual
{
int b = 12;
public virtual void Print()
{
Console.WriteLine("in base ({0})", this.b);
}
}
class DerivedVirtual : BaseVirtual
{
int d = 13;
public override void Print()
{
Console.WriteLine("in derived ({0})", this.d);
}
}
3) 用函數指針代替虛函數
仍然用上一篇文章的方法,用函數指針來實現虛函數的功能。
FrancisYoungBaseVirtualtypedef struct _FrancisYoungBaseVirtual
{
// basic info
Metadata * metaInfo;
// fields
int b;
// virtual methods
void (* pPrint4FrancisYoungBaseVirtual)(_FrancisYoungBaseVirtual *);
} FrancisYoungBaseVirtual;
// type id
static int FrancisYoungBaseVirtualTypeId = 14;
// method declarations
void Print4FrancisYoungBaseVirtual(FrancisYoungBaseVirtual * pThis);
// the new method
FrancisYoungBaseVirtual * NewFrancisYoungBaseVirtual()
{
// alloc for space
FrancisYoungBaseVirtual * pResult = (FrancisYoungBaseVirtual *)malloc(sizeof(FrancisYoungBaseVirtual));
// initialize basic info
pResult->metaInfo = NewMetadata(
pResult,
FrancisYoungBaseVirtualTypeId,
NULL);
// initialize fields
pResult->b = 12;
// initialize virtual methods
pResult->pPrint4FrancisYoungBaseVirtual = Print4FrancisYoungBaseVirtual;
// return result
return pResult;
}
void Print4FrancisYoungBaseVirtual(FrancisYoungBaseVirtual * pThis)
{
if (pThis == NULL) { return;/* throw exception in C# */ }
printf("in base (%d)\n", pThis->b);
}
FrancisYoungDerivedVirtualtypedef struct _FrancisYoungDerivedVirtual
{
// basic info
Metadata * metaInfo;
// fields
int d;
// virtual methods
} FrancisYoungDerivedVirtual;
// type id
static int FrancisYoungDerivedVirtualTypeId = 15;
// method declarations
void Print4FrancisYoungDerivedVirtual(FrancisYoungBaseVirtual * pThis);
// the new method
FrancisYoungDerivedVirtual * NewFrancisYoungDerivedVirtual()
{
// initialize base class
FrancisYoungBaseVirtual * pBase = NewFrancisYoungBaseVirtual();
// alloc for space
FrancisYoungDerivedVirtual * pResult = (FrancisYoungDerivedVirtual * )malloc(sizeof(FrancisYoungDerivedVirtual));
// initialize basic info
pResult->metaInfo = NewMetadata(
pResult,
FrancisYoungDerivedVirtualTypeId,
pBase->metaInfo);
// initialize fields
pResult->d = 13;
// initialize virtual methods
pBase->pPrint4FrancisYoungBaseVirtual = Print4FrancisYoungDerivedVirtual;
// return result
return pResult;
}
void Print4FrancisYoungDerivedVirtual(FrancisYoungBaseVirtual * pThis)
{
if (pThis == NULL) { return;/* throw exception in C# */ }
FrancisYoungDerivedVirtual * thisObj = (FrancisYoungDerivedVirtual*)Convert2Type(
pThis->metaInfo, FrancisYoungDerivedVirtualTypeId);
if (thisObj == NULL) { return;/* throw exception in C# */ }
printf("in derived %d\n", thisObj->d);
}
FrancisYoungManagerVirtualtypedef struct _FrancisYoungManagerVirtual
{
Metadata * metaInfo;
union
{
FrancisYoungBaseVirtual * pBase;
FrancisYoungDerivedVirtual * pDerived;
} obj;
} FrancisYoungManagerVirtual;
// type id
static int FrancisYoungManagerVirtualTypeId = 16;
// method declarations
// the new method
FrancisYoungManagerVirtual * NewFrancisYoungManagerVirtual()
{
// initialize base class
// alloc for space
FrancisYoungManagerVirtual * pResult = (FrancisYoungManagerVirtual * )malloc(sizeof(FrancisYoungManagerVirtual));
// initialize basic info
pResult->metaInfo = NewMetadata(
pResult,
FrancisYoungManagerVirtualTypeId,
NULL);
// initialize fields
pResult->obj.pBase = NewFrancisYoungBaseVirtual();
// initialize virtual methods
// return result
return pResult;
}
4) 使用ManagerVirtual類
C#代碼的調用方式如下。
ManagerVirtual m = new ManagerVirtual();
m.derived.Print();// In Base
對應的C代碼如下。
FrancisYoungManagerVirtual * m = NewFrancisYoungManagerVirtual();
FrancisYoungBaseVirtual * pBase = (FrancisYoungBaseVirtual*)Convert2Type(m->obj.pDerived->metaInfo, FrancisYoungBaseVirtualTypeId);
pBase->pPrint4FrancisYoungBaseVirtual(pBase);
仍然與之神似。
我們知道,虛方法在C代碼中,變成了一個函數指針。根據上一篇文章的分析,我們在這里創建的是父類的對象,所以Print的函數指針指向的是父類的Print方法,即C版代碼中的Print4FrancisYoungBaseVirtual方法。所以derived調用的只能是父類的Print4FrancisYoungBaseVirtual方法。
4. 結論
對《顛覆你對方法調用的看法!》的分析結果和原作者是一樣的,這也印證了我們上一篇文章的結論的正確性。
感想
上一篇文章我提到,為什么要把C封裝為面向對象語言?面向對象語言是如何從無到有的?最開始的那個人是怎么設計出這樣一套機制的?他之前沒有面向對象的任何概念,他的思路是什么?
通過本文的分析,我的感覺是,面向對象語言的創始人一定是在大量的編寫面向過程語言代碼的時候,逐漸感受到了現實世界和程序語言的嚴重脫節,也發現了兩者之間可能靠近一些的途徑。一個函數寫出來,如果幾乎不可能被其他人用在其他地方,那應該把它隱藏起來,這就是封裝的思想。
而繼承的思想是如何產生的,我還是不得而知。
源代碼在這里。