是否需要不兼容的指针来实现C中的多态性
我尝试使用以下代码在C中模拟C ++的多态性:
#include typedef struct Base { void (*out) (void); } Base; typedef struct Derived { Base base; int x; } Derived; void base_out() { printf("base\n"); } void derived_out() { printf("derived\n"); } void foo(void) { Derived d; Base b; b.out = base_out; d.base.out = derived_out; Base *p; p = &b; p->out(); //warning: assignment from incompatible pointer type p = &d; p->out(); } int main(void) { foo(); }
但是当p = &d
时,我必须做不兼容的指针赋值。 为了使其工作, Derived
base
成员也必须是第一个成员。 如何避免这两种情况?
不需要不兼容的指针赋值。 Per C 2011 [draft N1570] 6.7.2.1 15,“指向结构对象的指针,经过适当转换,指向其初始成员(或者如果该成员是位字段,则指向它所在的单位),以及反之亦然。“因此,当Derived
的第一个元素是Base
对象时,可以将指向Derived
的指针转换为指向Base
的指针。 并且指向作为Derived
第一个成员的Base
的指针可以转换为指向Derived
的指针。
所以这是合法的。 编译器可能会警告你这是安全的,因为它很不寻常。 使用显式强制转换可以避免:
p = (Base *) &d;
或者,您可以直接获取基地址:
p = &d.base;
但是,从Base返回到Derived仍需要强制转换:
Derived *x = (Derived *) p;
至于把基地放在除了作为第一个成员之外的某个地方,那么你就是在更加狡猾的地方。 派生到基地的方向仍然很容易; p = &d.base
很好。 换句话说,这可能就像你可以达到法定C一样接近:
-
获取指向基数的字符类型指针:
char *c = (char *) p
。 在C中,将任何指向对象的指针转换为指向字符类型的指针是合法的,这将为您提供指向对象的第一个字节的指针。 -
减去从
Derived
开始到Base
成员的字节数:c -= offsetof(Derived, base)
。 (这需要包括
。)因为c
指向Derived
Base
的第一个字节,它指向Derived
中的一个字节,并且你大部分(见下文)允许将Derived
视为一个字节数组,所以我们可以减去回到开头。offsetof
宏提供了该字节的字节数。 -
将字符指针转换为指向
Derived
的指针:Derived *x = (Derived *) c
。 在这里,我们处于不稳定的状态。 如果我们将指向Derived
的指针转换为字符指针,我们可以立即将其转换回来。 但是我们没有以这种方式得到这个指针。 它指向相同的字节,但我不清楚C标准是否支持这一点。 (实际上,上面的步骤2有一个类似的问题;一个对象可以解释为一个字符数组,但我们是从一个我不确定C标准实际支持的方向来实现的。)
把这些放在一起,转换将是:
Derived *x = (Derived *) ((char *) p - offsetof(Derived, base));
鉴于支持的不确定性,我只会在实验代码或课堂练习中使用它,除非我从我使用的C实现得到保证,支持这种指针算法。 最好坚持使用底座作为第一个元素,因为那些转换严格符合C.