Files
ViperOS/vpsdk/wiki/06-oop.md
2026-07-19 12:38:20 +08:00

18 KiB
Raw Permalink Blame History

06 - 面向对象与运算符重载

Viper 中的 class 有两种语义:数据布局(结构体/联合体/枚举,见 05-classes.md)和面向对象。面向对象特性通过 @t.Object 装饰器启用,多态通过 @t.CVTable 装饰器启用非多态继承C++ 风格,无 vtable 开销)通过 @t.NoVTable 装饰器启用。

@t.Object 面向对象

使用 @t.Object 装饰器启用面向对象特性,类将拥有成员方法、运算符重载、属性装饰器等 OOP 支持。编译器也会自动识别:如果结构体内有函数定义,会自动使用 @t.Object 行为,但在实际工程中更推荐明确标记,避免未定义行为。

基本定义

@t.Object
class ViperKernel:
    BootInfo: bootinfo | t.CPtr
    VGA: vga._VGAScreenDriver | t.CPtr
    VGA_drv: vga._VGAScreenDriver

    def __init__(self):
        self.BootInfo = BootInfo
        serial.init()
        serial.puts("ViperOS VKernel Test\n")

避免在 __init__ 中使用 return,虽然不会报错,但是你永远无法获得 return 的结果,如果需要提前结束代码流,return 也不失为一种办法。

成员方法

@t.Object
class Sheet:
    id: t.CInt
    x: t.CInt
    y: t.CInt
    w: t.CInt
    h: t.CInt

    def __init__(self, x: t.CInt, y: t.CInt, w: t.CInt, h: t.CInt):
        self.x = x
        self.y = y
        self.w = w
        self.h = h

    def MoveTo(self, nx: t.CInt, ny: t.CInt):
        self.x = nx
        self.y = ny

    def Resize(self, nw: t.CInt, nh: t.CInt):
        self.w = nw
        self.h = nh

方法调用

sheet: Sheet = Sheet(0, 0, 640, 480)
sheet.MoveTo(100, 100)
sheet.Resize(800, 600)

属性装饰器

支持 Python 的 @property@xxx.setter 装饰器:

@xxx.setter 和自定义装饰器正在试验阶段,尽可能不要使用

@t.Object
class MyObj:
    _value: t.CInt

    @property
    def value(self) -> t.CInt:
        return self._value

    @value.setter
    def value(self, v: t.CInt):
        self._value = v

静态方法

@staticmethod
def _yield():
    pass

对象指针与解引用

@t.Object 的实例通常通过指针操作:

obj_ptr: MyClass | t.CPtr = c.Addr(MyClass())
obj: MyClass = c.Deref(obj_ptr)

成员访问

通过 self 访问成员变量和方法:

def method(self) -> t.CVoid:
    self.x = 10
    y: t.CInt = self.y
    self.DoSomething()

内嵌对象

@t.Object 可以包含其他对象实例(非指针):

@t.Object
class Sheet:
    _surf_obj: gfx.Surface
    _renderer_obj: gfx.Renderer
    surf: gfx.Surface | t.CPtr
    renderer: gfx.Renderer | t.CPtr

    def __init__(self, fb: UINT32PTR, zb: float | t.CPtr, w: t.CInt, h: t.CInt):
        self._surf_obj = gfx.Surface(fb, zb, w, h)
        self.surf = c.Addr(self._surf_obj)
        self._renderer_obj = gfx.Renderer(self.surf)
        self.renderer = c.Addr(self._renderer_obj)

__before_init__ 方法

所有类(不限 @t.Object/@t.CVTable)自动生成 __before_init__ 方法,在 __init__ 之前自动填充成员默认值,并对 @t.CVTable 类初始化 vtable

define void @"<SHA1>.ClassName.__before_init__"(%"<SHA1>.ClassName"* %self) {
entry:
  ; 对 @t.CVTable 类:存储 vtable 指针到 slot 0
  ; 遍历所有成员:若有默认值则 store 默认值,否则 store 零值
  ret void
}

职责

  • 填充类定义中声明的成员默认值(如 x: t.CInt = 0 中的 0
  • 对无默认值的成员填充类型零值
  • @t.CVTable 类额外初始化 vtable 指针

调用时机(由编译器在构造时自动插入):

  1. alloca 栈分配后调用一次
  2. 若存在 __new__ 方法且返回堆指针对堆指针再调用一次alloca 的默认值已随栈帧丢弃,必须重新填充)
  3. __init__ 调用前

就地构造:用户也可手动调用 __before_init__ 实现就地构造placement new 语义),典型用法见 mpool.alloc_buf

buf: Buf | t.CPtr = pool.alloc(Buf.__sizeof__())
buf.__before_init__()          # 填充默认值
buf.__init__(args)             # 用户初始化逻辑

跨模块导入类时,导入侧也会声明 __before_init__,保证跨模块构造行为一致。

@t.NoVTable 非多态继承

@t.NoVTable 启用 C++ 风格的非多态继承(对应 struct A : B {} 无 virtual。与 @t.CVTable 的区别:

特性 @t.NoVTable @t.CVTable
vtable 指针 自动插入 slot 0
虚派发 支持
方法继承 生成包装函数转发 通过 vtable
字段展平 父类字段前置嵌入 父类字段前置嵌入vtable 之后)
运行时开销 每次虚调用一次间接跳转

基本定义

@t.NoVTable
class LinkedNode:
    next: "LinkedNode" | t.CPtr
    prev: "LinkedNode" | t.CPtr
    def append(self, node: "LinkedNode" | t.CPtr): ...

@t.NoVTable
class GNode(LinkedNode):    # 非多态继承
    value: t.CInt

子类 GNode 的结构体布局为 {next, prev, value}(父类字段前置,无 vtable 指针)。

方法继承机制

子类调用父类方法时,编译器通过 _generate_inherited_method_wrappers 自动生成包装函数:将 self bitcast 为父类指针后尾调用父类方法。例如 gnode.append(child) 会生成:

define void @"<SHA1>.GNode.append"(%"<SHA1>.GNode"* %self, ...) {
entry:
  %base = bitcast %"<SHA1>.GNode"* %self to %"<SHA1>.LinkedNode"*
  call void @"<SHA1>.LinkedNode.append"(%"<SHA1>.LinkedNode"* %base, ...)
  ret void
}

跨模块继承

跨模块父类(如 linkedlist.LinkedNode)的 @t.NoVTable 标记会在导入时被检测并记录,子类继承时同样跳过 vtable 插入。

向下转型

父类指针→子类指针需显式转型(生成 bitcast IR

root: Node = Node()                    # Node 继承 LinkedNode
child: LinkedNode | t.CPtr = root.child
# child 是 LinkedNode*,需要转回 Node*
node: Node | t.CPtr = (Node | t.CPtr)(child)

隐式转型仅向上(子→父)。

@t.CVTable 虚函数表与多态

尽可能避免使用 self[0] 来获取结构体成员,其不稳定,也不要手动操作虚表。

使用 @t.CVTable 装饰器可以启用虚函数表支持。启用后,结构体第一个成员自动插入 i8* 类型的 vtable 指针。@t.CVTable 的操作必须配合 @t.Object,但有时编译器会自动识别是否存在结构体内函数,自动使用 @t.Object

基本定义

@t.Object
@t.CVTable
class Base:
    def __init__(self):
        pass
    def method(self) -> t.CInt:
        return 0

在 LLVM IR 中,@t.CVTable 类的结构体类型自动插入 vtable 指针:

%"<SHA1>.Base" = type { i8*, i32 }

同时,编译器会生成全局 vtable 变量:

@"<SHA1>.Base_Vtable" = internal global [1 x i8*] zeroinitializer

继承与多态

虚表可以帮你使用继承语法实现多态:

@t.Object
@t.CVTable
class Base:
    def __init__(self):
        pass
    def method(self) -> t.CInt:
        return 0

@t.Object
@t.CVTable
class M1(Base):
    def method(self) -> t.CInt:
        return 1

当 M1 被初始化时VTable 会自动修改,这点和 C++ 相同,尽可能避免直接操作 VTable。

继承时子类会自动继承父类的成员变量和方法。子类重写父类方法时vtable 中对应位置的函数指针被子类方法替换,实现多态。

VTable 运行时修改

如果需要临时构造一个额外的 Base 结构体,还可以使用:

@t.Object
@t.CVTable
class Base:
    def __init__(self):
        pass
    def method(self) -> t.CInt:
        return 0

def __method1(self) -> t.CInt:
    return 1

def main() -> t.CInt | t.CExport:
    b: Base = Base()
    b.method = __method1

启用 VTable 的表中,允许直接修改内部函数为函数指针,在底层,此操作将修改 VTable 来达成目的。编译器会检测当前 vtable 是否与类 vtable 相同,如果相同则创建 vtable 的副本memcpy避免修改影响所有实例。

虚方法调度机制

虚方法调用的核心流程:

  1. 从对象指针 GEP 获取第 0 个 slotvtable 指针)
  2. 加载 vtable 指针
  3. bitcast 为 [N x i8*]* 类型
  4. GEP 获取方法索引处的函数指针
  5. 加载函数指针
  6. bitcast 为正确的函数指针类型
  7. 间接调用

继承

结构体成员继承

当子类继承父类(父类在 Gen.class_members 中存在)时:

  • 父类的成员变量被继承到子类(排在子类成员之前)
  • 父类的默认值也被继承
  • 父类的方法被继承(重命名为 子类名.方法名
@t.Object
class Animal:
    name: list[t.CChar, 32]
    age: t.CInt

    def __init__(self, age: t.CInt):
        self.age = age

    def Speak(self) -> t.CVoid:
        serial.puts("...\n")

@t.Object
class Dog(Animal):
    breed: t.CInt

    def Speak(self) -> t.CVoid:
        serial.puts("Woof!\n")

异常类继承

Viper 支持异常类的继承,用于 try/except 的异常匹配:

class MyError(Exception):
    pass

class SpecificError(MyError):
    pass

详见 09-exceptions.md

运算符重载

@t.Object 类支持运算符重载。编译器在遇到运算符时,首先检查左操作数是否为结构体类型,如果是则尝试调用对应的 dunder 方法。

算术运算符重载

运算符 Dunder 方法 示例
+ __add__ a + ba.__add__(b)
- __sub__ a - ba.__sub__(b)
* __mul__ a * ba.__mul__(b)
/ __truediv__ a / ba.__truediv__(b)
// __floordiv__ a // ba.__floordiv__(b)
% __mod__ a % ba.__mod__(b)
** __pow__ a ** ba.__pow__(b)

示例:

@t.Object
class Vec2:
    x: t.CFloat
    y: t.CFloat

    def __init__(self, x: t.CFloat, y: t.CFloat):
        self.x = x
        self.y = y

    def __add__(self, other: Vec2) -> Vec2:
        result: Vec2 = Vec2(0.0, 0.0)
        result.x = self.x + other.x
        result.y = self.y + other.y
        return result

    def __mul__(self, scalar: t.CFloat) -> Vec2:
        result: Vec2 = Vec2(0.0, 0.0)
        result.x = self.x * scalar
        result.y = self.y * scalar
        return result

增量赋值运算符重载

增量运算符 优先 Dunder 回退 Dunder
+= __iadd__ __add__
-= __isub__ __sub__
*= __imul__ __mul__
/= __itruediv__ __truediv__
//= __ifloordiv__ __floordiv__
%= __imod__ __mod__
**= __ipow__ __pow__

如果未定义增量版本(如 __iadd__),编译器自动回退到普通版本(如 __add__)。

比较运算符重载

实验性:以下比较运算符 dunder 方法在编译器中被识别,但尚未实现自动调度。定义它们不会报错,但比较运算不会自动调用。

运算符 Dunder 方法 状态
== __eq__ 仅类型推断识别
!= __ne__ 仅类型推断识别
< __lt__ 仅类型推断识别
<= __le__ 仅类型推断识别
> __gt__ 仅类型推断识别
>= __ge__ 仅类型推断识别

反向运算符重载

实验性:以下反向运算符 dunder 方法在编译器中被识别,但尚未实现自动调度。

Dunder 方法 预期语义 状态
__radd__ 右操作数加法 仅类型推断识别
__rsub__ 右操作数减法 仅类型推断识别
__rmul__ 右操作数乘法 仅类型推断识别
__rtruediv__ 右操作数真除法 仅类型推断识别
__rfloordiv__ 右操作数整除 仅类型推断识别
__rmod__ 右操作数取模 仅类型推断识别
__rpow__ 右操作数幂运算 仅类型推断识别

一元运算符重载

实验性:以下一元运算符 dunder 方法在编译器中被识别,但尚未实现自动调度。

运算符 Dunder 方法 状态
-a __neg__ 仅类型推断识别
+a __pos__ 仅类型推断识别

可调用对象 __call__

@t.Object
class Counter:
    value: t.CInt

    def __init__(self):
        self.value = 0

    def __call__(self) -> t.CInt:
        self.value += 1
        return self.value

c: Counter = Counter()
x: t.CInt = c()   # x = 1
y: t.CInt = c()   # y = 2

下标访问重载

__getitem__ — 读取

def __getitem__(self, key: t.CInt) -> t.CInt:
    return self.data[key]

__setitem__ — 赋值

def __setitem__(self, key: t.CInt, value: t.CInt):
    self.data[key] = value

__delitem__ — 删除

def __delitem__(self, key: t.CInt):
    pass

布尔转换 __bool__

@t.Object
class Buffer:
    size: t.CInt
    data: list[t.CChar, 1024]

    def __bool__(self) -> t.CBool:
        return self.size > 0

buf: Buffer = Buffer()
if buf:
    serial.puts("buffer is not empty\n")

长度 __len__

@t.Object
class Buffer:
    size: t.CInt

    def __len__(self) -> t.CInt:
        return self.size

buf: Buffer = Buffer()
n: t.CInt = len(buf)

字符串转换 __str__

@t.Object
class Point:
    x: t.CInt
    y: t.CInt

    def __str__(self) -> t.CConst | t.CChar | t.CPtr:
        return "Point"

p: Point = Point(1, 2)
print(p)          # 调用 p.__str__()

绝对值 __abs__

@t.Object
class SignedValue:
    val: t.CInt

    def __abs__(self) -> t.CInt:
        if self.val < 0:
            return -self.val
        return self.val

sv: SignedValue = SignedValue()
sv.val = -42
a: t.CInt = abs(sv)   # a = 42

迭代器协议

Viper 的迭代器协议与 Python 相同,但 __next__ 的实现方式通过 StopIteration 异常来结束。

__iter____next__

@t.Object
class IntRange:
    start: t.CInt
    end: t.CInt
    current: t.CInt

    def __init__(self, start: t.CInt, end: t.CInt):
        self.start = start
        self.end = end
        self.current = start

    def __iter__(self):
        return self

    def __next__(self, stop_flag) -> t.CInt:
        if self.current >= self.end:
            raise StopIteration
        val: t.CInt = self.current
        self.current += 1
        return val

for 循环使用

r: IntRange = IntRange(0, 10)
for i in r:
    print(i)

编译器生成的等价逻辑:

IntRange r = IntRange_init(0, 10);
IntRange iter = IntRange___iter__(&r);
while (1) {
    i1 stop_flag = 0;
    int i = IntRange___next__(&iter, &stop_flag);
    if (stop_flag == 1) break;
    print_int(i);
}

上下文管理器

with 语句要求对象实现 __enter____exit__ 方法:

@t.Object
class File:
    fd: t.CInt
    path: t.CConst | t.CChar | t.CPtr

    def __init__(self, path: t.CConst | t.CChar | t.CPtr, flags: t.CInt):
        self.path = path
        self.fd = -1

    def __enter__(self):
        self.fd = fat32.open(self.path, self.flags)
        return self

    def __exit__(self):
        fat32.close(self.fd)

    def read(self) -> t.CInt:
        return fat32.read(self.fd)

使用方式:

with File("/test.txt", FA_READ) as f:
    data: t.CInt = f.read()

编译器会自动保证 __exit__with 块结束时被调用。

析构 __del__ / __delete__

del obj        # 调用 obj.__del__() 或 obj.__delete__()
del arr[idx]   # 调用 arr.__delitem__(idx)

Dunder 方法完整参考

已实现自动调度的 Dunder 方法

Dunder 方法 触发语法 说明
__init__ ClassName(args) 构造函数
__before_init__ 自动生成 vtable 初始化,在 __init__ 之前执行
__add__ a + b 加法
__sub__ a - b 减法
__mul__ a * b 乘法
__truediv__ a / b 真除法
__floordiv__ a // b 整除
__mod__ a % b 取模
__pow__ a ** b 幂运算
__iadd__ a += b 增量加法(回退到 __add__
__isub__ a -= b 增量减法(回退到 __sub__
__imul__ a *= b 增量乘法(回退到 __mul__
__itruediv__ a /= b 增量真除法(回退到 __truediv__
__ifloordiv__ a //= b 增量整除(回退到 __floordiv__
__imod__ a %= b 增量取模(回退到 __mod__
__ipow__ a **= b 增量幂运算(回退到 __pow__
__call__ obj(args) 可调用对象
__getitem__ obj[key] 下标读取
__setitem__ obj[key] = value 下标赋值
__delitem__ del obj[key] 下标删除
__bool__ if obj: 布尔转换
__len__ len(obj) 长度
__str__ str(obj) / print(obj) 字符串转换
__abs__ abs(obj) 绝对值
__iter__ for x in obj: 获取迭代器
__next__ for x in obj: 获取下一个值(标志位方式)
__enter__ with obj as x: 上下文管理器进入
__exit__ with obj as x: 上下文管理器退出
__del__ del obj 析构
__delete__ del obj 删除描述符

声明支持但未实现自动调度的 Dunder 方法

Dunder 方法 预期触发语法 当前状态
__eq__ a == b 仅类型推断识别,比较运算不自动调度
__ne__ a != b 同上
__lt__ a < b 同上
__le__ a <= b 同上
__gt__ a > b 同上
__ge__ a >= b 同上
__neg__ -a 仅类型推断识别,一元负号不自动调度
__pos__ +a 同上
__radd__ 右操作数加法 仅类型推断识别
__rsub__ 右操作数减法 同上
__rmul__ 右操作数乘法 同上
__rtruediv__ 右操作数真除法 同上
__rfloordiv__ 右操作数整除 同上
__rmod__ 右操作数取模 同上
__rpow__ 右操作数幂运算 同上