学的语言多了,发现同一种目的,各个语言都有自己一套方法来解决。 就比如标题提到的 n 种方法都可以完成 code reuse 这个目的。

但是正常人就要问了:

花里胡哨的,都有啥区别?

我觉得还是有必要自己总结一下,学习亿下 语言设计 的智慧。

大坑,不定期啥时候填。

1. 简介

这几种方法,我们主要关注的有几点。这几点实现和细节会在 2. 章中细写。

1.1 继承

继承在 C++, Java, Python 里面都有,是一种很常见的 code reuse 手段。

特点有

问题是:

导致的 complexities 如

1.2 接口

在 Java 里面有,也很常见。

特点有

1.3 type class

1.4 delegation

1.5 module

1.6 trait

1.7 mixin

2. 编译器内部实现

2.1 继承

只讨论单继承,多继承参见 [2] 和 g++ 实现。

使用虚表实现。一种示例设计如下。

class A {
    int a;
    virtual void f1() { /* at addr A_f1 */ };
    virtual void f2() { /* at addr A_f2 */}; };
class B: public A {
    int b;
    virtual void f2() override { /* at addr B_f2 */};
    virtual void f3() = 0; };
// 提一句:假设 A *a = new A; 那 a.f1() 实际上是调用一个函数 A::f1(a)
// 即:method 本质是函数

那么 B 和 A 对象在内存里表示为

         B OBJECT              VTABLE
        +-----------+         +----------+
new B-> | vtable<B> +-------> | A_f1     |
        +-----------+         +----------+
        | a         |         | B_f2     |
        +-----------+         +----------+
        | b         |         | NULL     |
        +-----------+         +----------+

        A OBJECT               VTABLE
        +-----------+         +----------+
new A-> | vtable<B> +-------> | A_f1     |
        +-----------+         +----------+
        | a         |         | A_f2     |
        +-----------+         +----------+

调用虚函数的时候,真正要跳转到的函数地址要去虚表里面查。 例如调用 A::f2(A* a),我们传参 a 以后跳转到 (a[0])[1]

A::f2(A* a)
    mov a, %rdi
    mov 0(a), %rax
    mov 8(%rax), %rax
    call *%rax

这样我们能保证 A* a = new B; a->f2(); 一定调用的是 B 重写的 f2

2.2 接口

TODO: itable

2.3 type class

2.4 delegation

2.5 module

2.6 trait

2.7 mixin

3. 比较

TODO

References

[1] Wikipedia

[2] Modern Compiler Implementation in Java. 2e

[3] Programming Language Pragmatics. 4e