返回介绍

8.3.4 多态

发布于 2024-10-13 11:25:31 字数 2954 浏览 0 评论 0 收藏 0

通常我们会以接口(interface)、基类(base/super class)或泛型(generic)实现多态,为不同类型提供统一调用方式。特征作为这些的 “混合体”,无疑满足需求。

  • 泛型:编译期多态。
  • 特征:运行期多态。

静态分派

以泛型实现 “单态化”(monomorphization)静态分派(static dispatch),为目标分别创建专用版本。虽然生成更多代码,但不会降低性能,且允许内联。

trait TestTrait {
  fn test(&self);
}

impl TestTrait for i32 {
  fn test(&self) { println!("{}", self); }
}

impl TestTrait for &str {
  fn test(&self) { println!("{}", self); }
}

/* ----------------------------------------*/

// 泛型函数。
fn call<T: TestTrait>(x: T) {   
  x.test();
}

fn main() {
  call(123);
  call("abc");
}
(gdb) disass/s

fn main() {

  call(123);
  
  => mov  edi,0x7b
   call   0x555555567300 <demo::call<i32>>

  call("abc");
  
   lea  rdi,[rip+0xffffffffffffb05a]   # 0x55555556246c
   mov  esi,0x3
   call   0x5555555672c0 <demo::call<&str>>
   
}

动态分派

如果被转换为 特征对象 (trait object),那么只有在执行时才能知道 具体类型 。这不会生成多个副本,而是通过虚拟调用(virtual function calls)实现 动态分派 (dynmaic dispatch)。动态方式会禁止某些优化,禁止内联,性能确有一些影响,但更加灵活。

trait TestTrait {
  fn test(&self);
}

impl TestTrait for i32 {
  fn test(&self) { println!("{}", self); }
}

impl TestTrait for &str {
  fn test(&self) { println!("{}", self); }
}

/* ---------------------------------------------------*/

fn call(x: &dyn TestTrait) {   // 将实参转换为 trait 对象。
  x.test();
}

fn main() {
  call(&123);
  call(&"abc");
}

基本结构:

struct trait_object {
  pointer: *mut (),    // rdi: lea data (void*)
  vtable : *mut (),    // rsi: vtable
}

struct vtable {
  destructor: fn(*mut ()), // 析构
  size    : usize,     // 目标类型大小
  align   : usize,     // 目标类型对齐
  method  : fn(),    // 方法表   +0x18
}

并未像泛型那样生成多个副本,而是将参数转换为特征对象。

(gdb) disass/s

fn main() {

  call(&123);
  
  => lea  rdi,[rip+0xfffffffffffff85c]   # 0x555555566c04
   lea  rsi,[rip+0x37611]        # 0x55555559e9c0
   call   0x555555567380 <demo::call>

  call(&"abc");
  
   lea  rdi,[rip+0x37625]       # 0x55555559e9e0
   lea  rsi,[rip+0x3762e]       # 0x55555559e9f0
   call   0x555555567380 <demo::call>

}
(gdb) x/xw 0x555555566c04   ; .pointer
0x555555566c04:  0x0000007b  ; 0x7b = 123

(gdb) x/4xg 0x55555559e9c0  ; .vtable
0x55555559e9c0: 0x0000555555567470    0x0000000000000004
0x55555559e9d0: 0x0000000000000004    0x00005555555672c0

(gdb) info symbol 0x0000555555567470
core::ptr::drop_in_place<i32>

(gdb) info symbol 0x00005555555672c0
<i32 as demo::TestTrait>::test

通过 vtable + 0x18 获取目标方法地址。

(gdb) disass/s 0x555555567380

fn call(x: &dyn TestTrait) {

   sub  rsp,0x18
   mov  QWORD PTR [rsp+0x8],rdi   ; pointer
   mov  QWORD PTR [rsp+0x10],rsi  ; vtable

  x.test();
   call   QWORD PTR [rsi+0x18]    ; vtable + 0x18

}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文