Files
TransPyC/wiki/06-oop.md
2026-07-18 19:25:40 +08:00

693 lines
18 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 06 - 面向对象与运算符重载
Viper 中的 `class` 有两种语义:**数据布局**(结构体/联合体/枚举,见 [05-classes.md](05-classes.md))和**面向对象**。面向对象特性通过 `@t.Object` 装饰器启用,多态通过 `@t.CVTable` 装饰器启用非多态继承C++ 风格,无 vtable 开销)通过 `@t.NoVTable` 装饰器启用。
## `@t.Object` 面向对象
使用 `@t.Object` 装饰器启用面向对象特性,类将拥有成员方法、运算符重载、属性装饰器等 OOP 支持。编译器也会自动识别:如果结构体内有函数定义,会自动使用 `@t.Object` 行为,但在实际工程中更推荐明确标记,避免未定义行为。
### 基本定义
```python
@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` 也不失为一种办法。
### 成员方法
```python
@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
```
### 方法调用
```python
sheet: Sheet = Sheet(0, 0, 640, 480)
sheet.MoveTo(100, 100)
sheet.Resize(800, 600)
```
### 属性装饰器
支持 Python 的 `@property``@xxx.setter` 装饰器:
> `@xxx.setter` 和自定义装饰器正在试验阶段,尽可能不要使用
```python
@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
```
### 静态方法
```python
@staticmethod
def _yield():
pass
```
### 对象指针与解引用
`@t.Object` 的实例通常通过指针操作:
```python
obj_ptr: MyClass | t.CPtr = c.Addr(MyClass())
obj: MyClass = c.Deref(obj_ptr)
```
### 成员访问
通过 `self` 访问成员变量和方法:
```python
def method(self) -> t.CVoid:
self.x = 10
y: t.CInt = self.y
self.DoSomething()
```
### 内嵌对象
`@t.Object` 可以包含其他对象实例(非指针):
```python
@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
```llvm
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`
```python
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 之后) |
| 运行时开销 | 零 | 每次虚调用一次间接跳转 |
### 基本定义
```python
@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)` 会生成:
```llvm
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
```python
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`
### 基本定义
```python
@t.Object
@t.CVTable
class Base:
def __init__(self):
pass
def method(self) -> t.CInt:
return 0
```
在 LLVM IR 中,`@t.CVTable` 类的结构体类型自动插入 vtable 指针:
```llvm
%"<SHA1>.Base" = type { i8*, i32 }
```
同时,编译器会生成全局 vtable 变量:
```llvm
@"<SHA1>.Base_Vtable" = internal global [1 x i8*] zeroinitializer
```
### 继承与多态
虚表可以帮你使用继承语法实现多态:
```python
@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` 结构体,还可以使用:
```python
@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` 中存在)时:
- 父类的成员变量被继承到子类(排在子类成员之前)
- 父类的默认值也被继承
- 父类的方法被继承(重命名为 `子类名.方法名`
```python
@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` 的异常匹配:
```python
class MyError(Exception):
pass
class SpecificError(MyError):
pass
```
详见 [09-exceptions.md](09-exceptions.md)。
## 运算符重载
`@t.Object` 类支持运算符重载。编译器在遇到运算符时,首先检查左操作数是否为结构体类型,如果是则尝试调用对应的 dunder 方法。
### 算术运算符重载
| 运算符 | Dunder 方法 | 示例 |
|--------|------------|------|
| `+` | `__add__` | `a + b``a.__add__(b)` |
| `-` | `__sub__` | `a - b``a.__sub__(b)` |
| `*` | `__mul__` | `a * b``a.__mul__(b)` |
| `/` | `__truediv__` | `a / b``a.__truediv__(b)` |
| `//` | `__floordiv__` | `a // b``a.__floordiv__(b)` |
| `%` | `__mod__` | `a % b``a.__mod__(b)` |
| `**` | `__pow__` | `a ** b``a.__pow__(b)` |
示例:
```python
@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__`
```python
@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__` — 读取
```python
def __getitem__(self, key: t.CInt) -> t.CInt:
return self.data[key]
```
### `__setitem__` — 赋值
```python
def __setitem__(self, key: t.CInt, value: t.CInt):
self.data[key] = value
```
### `__delitem__` — 删除
```python
def __delitem__(self, key: t.CInt):
pass
```
## 布尔转换 `__bool__`
```python
@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__`
```python
@t.Object
class Buffer:
size: t.CInt
def __len__(self) -> t.CInt:
return self.size
buf: Buffer = Buffer()
n: t.CInt = len(buf)
```
## 字符串转换 `__str__`
```python
@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__`
```python
@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__`
```python
@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 循环使用
```python
r: IntRange = IntRange(0, 10)
for i in r:
print(i)
```
编译器生成的等价逻辑:
```c
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__` 方法:
```python
@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)
```
使用方式:
```python
with File("/test.txt", FA_READ) as f:
data: t.CInt = f.read()
```
编译器会自动保证 `__exit__``with` 块结束时被调用。
## 析构 `__del__` / `__delete__`
```python
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__` | 右操作数幂运算 | 同上 |