我有我自定义的小 OOP 式继承功能,像这样:
// base class
struct BaseTag;
typedef struct {
int (*DoAwesomeStuff)(struct BaseTag* pInstance);
} S_BaseVtable;
typedef struct BaseTag{
S_BaseVtable* pVtable;
int AwesomeValue;
} S_Base;
// child class
struct ChildTag;
typedef struct {
S_BaseVtable Base;
void (*SomeOtherStuff)(struct ChildTag* pInstance);
} S_ChildVTable;
typedef struct ChildTag {
S_Base BaseClass;
int EvenAwesomerValue;
} S_Child;
现在假设我有一个子类构造函数,其中基类 vtable 被子 vtable 覆盖:
void Child_ctor(S_Child* pInstance) {
Base_ctor((S_Base*) pInstance);
pInstance.BaseClass.pVtable = (S_BaseVtable*) &MyChildVTable;
}
同样在这个子vtable中,我想用这样的方法覆盖基类的
DoAwesomeStuff()
方法:
int Child_DoAwesomeStuff(struct BaseTag* pInstance) {
S_Child* pChild = (S_Child*) pInstance; // undefined behaviour
return pChild->EvenAwesomerValue;
}
我偶尔会看到这种模式的变化,但我发现它存在一些问题。我的主要问题是
S_ChildVtable
指针后面的子实例访问 S_BaseVtable
?pInstance
的 Child_DoAwesomeStuff()
参数转换为 S_Child*
类型?据我了解 C 标准,从
S_Child*
到 S_Base*
(以及相应的 vtable 类型)是可以的,因为 S_Child
的第一个成员是一个 S_Base
实例。但反之亦然,这是未定义的行为。
像
S_Child* pChild = (S_Child*)((char*) pInstance)
这样的东西是合法的和定义的吗?
只要传递给
pInstance
的 Child_DoAwesomeStuff()
指针实际上最初构造为 S_Child
对象,然后将指针强制转换回 S_Child
指针 (S_Child*
) 是 not 未定义的行为。如果您尝试将指向最初构造为 struct BaseTag
(akaS_Base
)类型的对象的指针转换为子指针类型,这只会是未定义的行为。
这也是 C++ 的工作方式,使用
dynamic_cast<>()
(我在这里的回答中提到)。
来自https://cplusplus.com/doc/tutorial/typecasting/“dynamic_cast”部分下的示例 C++ 代码如下。
注意
pba
和pbb
都是指向基类型(Base *
)的指针,然而,pba
实际上是constructed作为Derived
(子)类型通过new Derived
,而pbb
实际上是 constructed 通过 Base
.因此,将 new Base
转换为
pba
是完全有效的,因为它确实是那种类型,但是将 Derived*
转换为
pbb
是 not有效的,因为它 not 确实是那种类型. C++ 的
Derived*
调用在运行时捕捉到这个未定义的行为,检测到返回的类型不是完全形成的 dynamic_cast<Derived*>(pbb)
类型,并返回一个 Derived
,它等于 nullptr
,所以你得到打印那说0
(代码下方继续讨论)
Null pointer on second type-cast.
输出:
// dynamic_cast
#include <iostream>
#include <exception>
using namespace std;
class Base { virtual void dummy() {} };
class Derived: public Base { int a; };
int main () {
try {
Base * pba = new Derived;
Base * pbb = new Base;
Derived * pd;
pd = dynamic_cast<Derived*>(pba);
if (pd==0) cout << "Null pointer on first type-cast.\n";
pd = dynamic_cast<Derived*>(pbb);
if (pd==0) cout << "Null pointer on second type-cast.\n";
} catch (exception& e) {cout << "Exception: " << e.what();}
return 0;
}
同样,您的代码具有相同的行为。
这样做是有效的:
Null pointer on second type-cast.
但是这样做是不
好的:
// create a child
S_Child child;
// treat it like a base (ok since `S_Base` is at the beginning of it--since the
// child contains a base object)
S_Base* pBase = (S_Base*)&child;
// Now obtain the child back from the base pointer
S_Child* pChild = (S_Child*)pBase; // ok, since pBase really points to a
// child object
困难!没有人知道的索引器(包括 Eclipse,并且 Eclipse 有我见过的最好的索引器)可以追溯到代码中哪个函数或类型被分配给了一个指针。除非你做这些只是为了学习练习,或者是为了在 C 中从头开始引导你自己的 C++ 语言(同样,为了学习),否则我建议不要使用这些模式。 如果你想要“面向对象
”具有继承和所有功能的 C,请不要这样做。如果你想要“object-based”C,通过不透明的指针/结构进行基本的私有成员封装和数据隐藏,那很好!以下是我更喜欢这样做的方式:Option 1.5(“基于对象的”C 架构).