用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)
", 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)
", 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)
", 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
", 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封装为面向对象语言?面向对象语言是如何从无到有的?最开始的那个人是怎么设计出这样一套机制的?他之前没有面向对象的任何概念,他的思路是什么?
通过本文的分析,我的感觉是,面向对象语言的创始人一定是在大量的编写面向过程语言代码的时候,逐渐感受到了现实世界和程序语言的严重脱节,也发现了两者之间可能靠近一些的途径。一个函数写出来,如果几乎不可能被其他人用在其他地方,那应该把它隐藏起来,这就是封装的思想。
而继承的思想是如何产生的,我还是不得而知。
源代码在这里。