6.14 简论多重继承、指针与类型检查

当目标脚本语言引用C++对象时,它通常使用一个标记指针对象,该对象包含指针的值和类型的字符串。例如,在Tcl中,一个C++指针可能编码成像这样的字符串:

  1. _808fea88_p_Circle

一个常见的问题是,是否可以安全地从指针中删除类型标记。例如,为了获得更好的性能,是否可以取消所有类型的标记,而只使用简单的整数代替呢?

总的来说,这个问题的答案是否定的。在包装器中,所有指针都转换成目标语言中的通用数据表示形式。通常,这相当于将指针转换成void*。这意味着与指针相关联的任何C++类型信息都在转换中丢失。丢失类型信息的问题是不能再正确支持许多高级C++特性——尤其是多重继承。例如,假设您有这样的代码:

  1. class A {
  2. public:
  3. int x;
  4. };
  5. class B {
  6. public:
  7. int y;
  8. };
  9. class C : public A, public B {
  10. };
  11. int A_function(A *a) {
  12. return a->x;
  13. }
  14. int B_function(B *b) {
  15. return b->y;
  16. }

现在考虑如下使用void*的代码:

  1. C *c = new C();
  2. void *p = (void *) c;
  3. ...
  4. int x = A_function((A *) p);
  5. 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*类型的指针后能够用同样的类型转换回来而不丢失类型信息。

  1. C *c = new C();
  2. void *p = (void *) c;
  3. void *pA = (void *) c;
  4. void *pB = (void *) c;
  5. ...
  6. int x = A_function((A *) pA);
  7. int y = B_function((B *) pB);

实际上,指针在目标语言代理类中是一个整数。