6.14 简论多重继承、指针与类型检查
当目标脚本语言引用C++对象时,它通常使用一个标记指针对象,该对象包含指针的值和类型的字符串。例如,在Tcl中,一个C++指针可能编码成像这样的字符串:
_808fea88_p_Circle
一个常见的问题是,是否可以安全地从指针中删除类型标记。例如,为了获得更好的性能,是否可以取消所有类型的标记,而只使用简单的整数代替呢?
总的来说,这个问题的答案是否定的。在包装器中,所有指针都转换成目标语言中的通用数据表示形式。通常,这相当于将指针转换成void*
。这意味着与指针相关联的任何C++类型信息都在转换中丢失。丢失类型信息的问题是不能再正确支持许多高级C++特性——尤其是多重继承。例如,假设您有这样的代码:
class A {
public:
int x;
};
class B {
public:
int y;
};
class C : public A, public B {
};
int A_function(A *a) {
return a->x;
}
int B_function(B *b) {
return b->y;
}
现在考虑如下使用void*
的代码:
C *c = new C();
void *p = (void *) c;
...
int x = A_function((A *) p);
int y = B_function((B *) p);
在这段代码中,函数A_function()
和B_function()
接受类型为C*
的对象都是合法的。但是,其中一个函数将总是返回错误的结果。原因是,尽管p
指向类型C
,但强制转换操作并没有按照你的预期工作。在内部,这与C的数据表示有关。通过多重继承,每个基类的数据堆积在一起。例如:
—————— <—- (C ), (A ) | A | |——————| <—- (B *) | B | -—————-
正因为这样的堆积方式,一个类型为C*
的指针被转换成A*
或B*
后可以改变值。但是,当从void*
转换后,这种调整就不会发生。
使用类型标记使得所有的指针都编程底层对象的真正指针。这个额外的信息之后被SWIG使用生成包装代码,它使其能正确的在继承体系下转换指针(避免上面的问题)。
一些语言模块可以通过存储指针的多个实例来解决这个问题,例如,在A的代理类中存储A*
,在C的代理类中存储C*
。通过选择正确的void *
指针,转换得以正确执行,保证装换成void*
类型的指针后能够用同样的类型转换回来而不丢失类型信息。
C *c = new C();
void *p = (void *) c;
void *pA = (void *) c;
void *pB = (void *) c;
...
int x = A_function((A *) pA);
int y = B_function((B *) pB);
实际上,指针在目标语言代理类中是一个整数。