在子类型上调用函数是如何实现的?
How is calling a function on a subtype implemented?
我目前正在设计一种编程语言,我很好奇如何解决这个问题:
假设我有一个 class(或接口)A,看起来像这样:
class A { // size is 4 bytes
int32 a = 0;
}
和第二个 class B 扩展它,看起来像这样:
class B extends A { // size is 8 bytes
int32 b = 0;
}
并且我有一个如下所示的函数 f:
int32 f(A first, A second) {
return first.a + second.a;
}
但是,如果我用两个 B 调用它,second.a 将不会与用两个 A 调用它时位于同一位置,因为第一个参数会移动它。我目前解决这个问题的想法是:
- 不允许未知大小的参数,并强制将其作为指针或引用传递(我认为这就是 Rust 所做的)
- 正在将以下所有信息写入调用堆栈:指向第二个的指针、指向第二个之后的指针、非可变大小参数、第一个、第二个
- 为 first 和 second 的每个可能大小创建一个函数,并在编译时(如果已知)或在运行时使用 vtables 确定调用哪个函数。
第二个想法将是一个问题,因为它需要所有函数的支持,即使它们很少或从不使用子类型调用,这是低效的。
第三个想法需要创建很多函数(一个函数接受 5 个参数,可以是 20 种不同的子类型,如果只调用一个未知的代码,则需要生成 100 段类似的代码- type params),并且每个只有一个函数使用它的 class 都需要一个 vtable。此外,已编译库中的函数无法与新子类型一起使用。
结合 2 和 3 并创建相同函数的两个版本,一个只接受类型,另一个也接受子类型可以解决其中的一些问题。
我很好奇是否有更好的解决方案,以及 C++ 等其他语言如何实现这一点。
在 C++ 中,使用子类型 B
的参数按值调用 f(A)
等同于
f(static_cast<const A&>(b));
static_cast
可能导致同一地址的内存被重新解释为更短数据块的开始,或者首先透明地添加一些偏移量(如果 A
不是第一个base class 或者是虚拟的)。之后,在内部调用 A
的复制构造函数。在任何一种情况下,由 B
添加的信息以及虚函数的覆盖都将完全丢失。出于所有目的,传递的不再是 B
.
动态多态性需要引用或指针,这与您概述的原因很相似。但是,如果您想传递 "reference by value",最简单的解决方案可能是传递对对象副本的引用。请注意,在这种情况下,每个对象都需要 "know" 调用正确的复制构造函数的类型,或者从公共 superclass 派生并实现某种形式的 clone()
.
我目前正在设计一种编程语言,我很好奇如何解决这个问题:
假设我有一个 class(或接口)A,看起来像这样:
class A { // size is 4 bytes
int32 a = 0;
}
和第二个 class B 扩展它,看起来像这样:
class B extends A { // size is 8 bytes
int32 b = 0;
}
并且我有一个如下所示的函数 f:
int32 f(A first, A second) {
return first.a + second.a;
}
但是,如果我用两个 B 调用它,second.a 将不会与用两个 A 调用它时位于同一位置,因为第一个参数会移动它。我目前解决这个问题的想法是:
- 不允许未知大小的参数,并强制将其作为指针或引用传递(我认为这就是 Rust 所做的)
- 正在将以下所有信息写入调用堆栈:指向第二个的指针、指向第二个之后的指针、非可变大小参数、第一个、第二个
- 为 first 和 second 的每个可能大小创建一个函数,并在编译时(如果已知)或在运行时使用 vtables 确定调用哪个函数。
第二个想法将是一个问题,因为它需要所有函数的支持,即使它们很少或从不使用子类型调用,这是低效的。
第三个想法需要创建很多函数(一个函数接受 5 个参数,可以是 20 种不同的子类型,如果只调用一个未知的代码,则需要生成 100 段类似的代码- type params),并且每个只有一个函数使用它的 class 都需要一个 vtable。此外,已编译库中的函数无法与新子类型一起使用。
结合 2 和 3 并创建相同函数的两个版本,一个只接受类型,另一个也接受子类型可以解决其中的一些问题。
我很好奇是否有更好的解决方案,以及 C++ 等其他语言如何实现这一点。
在 C++ 中,使用子类型 B
的参数按值调用 f(A)
等同于
f(static_cast<const A&>(b));
static_cast
可能导致同一地址的内存被重新解释为更短数据块的开始,或者首先透明地添加一些偏移量(如果 A
不是第一个base class 或者是虚拟的)。之后,在内部调用 A
的复制构造函数。在任何一种情况下,由 B
添加的信息以及虚函数的覆盖都将完全丢失。出于所有目的,传递的不再是 B
.
动态多态性需要引用或指针,这与您概述的原因很相似。但是,如果您想传递 "reference by value",最简单的解决方案可能是传递对对象副本的引用。请注意,在这种情况下,每个对象都需要 "know" 调用正确的复制构造函数的类型,或者从公共 superclass 派生并实现某种形式的 clone()
.