标签 C++ 下的文章

Rust 中的 trait 很神奇,有静态和动态两种用法,当静态使用时相当于 C++20 中引入的概念,动态使用时又相当于抽象基类,或者说是接口。
虽然 trait 可以派生自另一个 trait,结构体可以实现 trait,但是结构体不支持继承和派生,对于一个用惯了 C++ 的程序员来说,多少还是有些不适应,于是就想着用宏来模拟类的功能。
为简化实现,我不打算支持多重继承、私有继承、保护继承等不常用的特性,也不支持在同一个类中定义参数不同的同名函数,在 C++ 中叫做函数重载。
我想实现的效果如下,通过给结构体添加属性 #[class] 来提供类的功能,通过在函数前添加关键字 virtual 来声明虚方法,通过关键字 override 来重写基类的虚方法:

#[class]
pub struct Base
{
    x: i32,
    y: i32,
    fn new(x: i32, y: i32) -> Self { Base{ x, y } }
    virtual fn func1(&self) -> i32 { self.x }
    virtual fn func2(&self, i: i32) -> i32 { self.y + i }
}
#[class]
pub struct Derive1 : Base
{
    z: i32,
    fn new(x: i32, y: i32, z: i32) -> Self { Derive1 { Base::new(x, y), z} }
    override fn func1(&self) -> i32 { self.z }
    virtual fn func3(&self) -> i32 { self.z }
}
#[class]
pub struct Derive2 : Derive1
{
    override fn func2(&self, i: i32) -> i32 { Base::func2(self, i) + 200 }
    override fn func3(&self) -> i32 { Derive1::func3(self) + 200 }
}

为了实现虚函数及重载,需要我们自己来构建虚函数表,并在类中添加虚指针,Base 类可能会展开为如下的形式:

#[repr(C)]
pub struct BaseData
{
    x: i32,
    y: i32,
}
#[repr(C)]
pub struct BaseVTable
{
    func1: fn(this: &Base) -> i32,
    func2: fn(this: &Base, i: i32) -> i32,
}
#[repr(C)]
pub struct Base
{
    vptr: &'static BaseVTable,
    data: BaseData,
}
impl BaseData
{
    fn new(x: i32, y: i32) -> Self { BaseData{ x, y } }
}
impl Base
{
    const VTABLE: BaseVTable = BaseVTable
    {
        func1: Self::func1_impl,
        func2: Self::func2_impl,
    };
    pub fn new(x: i32, y: i32) -> Self
    {
        Base { vptr: &Self::VTABLE, data: BaseData::new(x, y) }
    }
    fn func1_impl(this: &Base) -> i32 { this.data.x }
    fn func2_impl(this: &Base, i: i32) -> i32 { this.data.y + i }
    pub fn func1(&self) -> i32 { (self.vptr.func1)(self) }
    pub fn func2(&self, i: i32) -> i32 { (self.vptr.func2)(self, i) }
}

一切都很完美,然后 Derive1 类应该展开为如下的形式:

#[repr(C)]
struct Derive1Data
{
    base: BaseData,
    z: i32,
}
#[repr(C)]
struct Derive1VTable
{
    func1: fn(this: &Base) -> i32,
    func2: fn(this: &Base, i: i32) -> i32,
    func3: fn(this: &Derive1) -> i32,
}
#[repr(C)]
pub struct Derive1
{
    vptr: &'static Derive1VTable,
    data: Derive1Data,
}
impl Derive1Data
{
    fn new(x: i32, y: i32, z: i32) -> Self { Derive1Data{ base: BaseData::new(x, y), z } }
}
impl Derive1
{
    const VTABLE: Derive1VTable = Derive1VTable
    {
        func1: Self::func1_impl,
        func2: Base::VTABLE.func2,
        func3: Self::func3_impl,
    };
    pub fn new(x: i32, y: i32, z: i32) -> Self
    {
        Derive1 { vptr: &Self::VTABLE, data: Derive1Data::new(x, y, z) }
    }
    fn func1_impl(this: &Base) -> i32
    {
        let this: &Self = unsafe { reinterpret_cast(this) };
        this.data.z
    }
    fn func3_impl(this: &Derive1) -> i32 { this.data.z }
    pub fn func3(&self) -> i32 { (self.vptr.func3)(self) }
}

我们看到对于 Derive1 的数据成员和虚函数表,我们采用了不同的方式,是因为我将数据成员为认定为私有的,派生类不可以直接访问,所以在派生类中访问基类的数据成员,需要多个 base. 我并不关心,也可以避免派生类和基类数据成员的命名冲突。
对于虚函数表,我不能接受 base.base.base.base.func1 = Self::func1_impl 这样的写法,不仅仅是丑的问题,最主要的是,我不知道需要多少个 base. 才能访问到 func1,为此我需要将基类的函数表展开到派生类,接下来是 Derive2 的展开:

type Derive2Data = Derive1Data;
type Derive2VTable = Derive1VTable;
#[repr(C)]
pub struct Derive2
{
    vptr: &'static Derive2VTable,
    data: Derive2Data,
}
impl Derive2
{
    const VTABLE: Derive2VTable = Derive2VTable
    {
        func1: Derive1::VTABLE.func1,
        func2: Self::func2_impl,
        func3: Self::func3_impl,
    };
    pub new(x: i32, y: i32, z: i32) -> Self
    {
        Derive2 { vptr: &Self::VTABLE, data: Derive2Data(x, y, z) }
    }
    fn func2_impl(this: &Base, i: i32) -> i32 { (Base::VTABLE.func2)(this, i) + 200 }
    fn func3_impl(this: &Derive1) -> i32 { (Derive1::VTABLE.func3)(this) + 200 }
}

由于 Derive2 没有数据成员和新增虚函数,所以数据和虚函数表的定义直接使用 Derive1 的就好了。
接下来到了验证阶段,我们来看下,我们实现的类及虚函数重写机制是否能正常工作:

#[cfg(test)]
mod tests
{
    fn func(base: &super::Base) -> (i32, i32)
    { (base.func1(), base.func2(100)) }
    fn func2(d1: &super::Derive1) -> (i32, i32, i32)
    { (d1.func1(), d1.func2(100), d1.func3() }
    #[test]
    fn test_fn()
    {
        let b = super::Base::new(1, 2);
        assert_eq!((1, 102), func(&b));
        let d1 = super::Derive1::new(1, 2, 3);
        assert_eq!((3, 102), func(&d1));
        assert_eq!((3, 102, 3), func2(&d1));
        let d2 = super::Derive2::new(1, 2, 3);
        assert_eq!((3, 302), func(&d2));
        assert_eq!((3, 302, 203), func2(&d2));
    }
}

很不幸,测试代码还不能工作。Rust 还不能理解三个类之间的关系,需要我们给类和基类之间建立联系,但在这之前,有两个基础函数,需要现行实现,如下:

#[inline(always)]
pub unsafe fn reinterpret_cast<'a, T, U>(t: &'a T) -> &'a U
{
    let p = t as *const T;
    &*(p as *const U)
}
#[inline(always)]
pub unsafe fn reinterpret_cast_mut<'a, T, U>(t: &'a mut T) -> &'a mut U
{
    let p = t as *mut T;
    &mut *(p as *mut U)
}

对于 C++ 程序员来说,可能会觉得眼熟。方法 reinterpret_cast 和 reinterpret_cast_mut,正如它们的名字一样,它们可以将任意一种类型转换为另外一种类型,而不做安全性检查,因此它们是不安全的。接下来我们要用这两个不安全的函数来做一些不寻常的事情。

use std::ops::Deref;
use std::ops::DerefMut;
impl Deref for Derive1
{
    type Target = Base;
    fn deref(&self) -> &Self::Target
    {
        unsafe { reinterpret_cast(self) }
    }
}
impl DerefMut for Derive1
{
    fn deref_mut(&mut self) -> &mut Self::Target
    {
        unsafe { reinterpret_cast_mut(self) }
    }
}
impl Deref for Derive2
{
    type Target = Derive1;
    fn deref(&self) -> &Self::Target
    {
        unsafe { reinterpret_cast(self) }
    }
}
impl DerefMut for Derive2
{
    fn deref_mut(&mut self) -> &mut Self::Target
    {
        unsafe { reinterpret_cast_mut(self) }
    }
}

我们为派生类实现 Deref 和 DerefMut 两个 triat,让派生类引用能够转换为基类的引用,而实现方法是 reinterpret_cast,可以说我们是在挑战 Rust 的安全性。我们可以这样做,是因为我们确信派生类和基类在基类大小的部分有着相同的内存布局。这也是我们给生成的结构体添加 #[repr(C)] 属性的原因。
现在,我们测试可以通过了,我们可以将派生类的引用转为基类的引用,也可以通过基类的引用来调用派生类的方法。
但是,如果我希望将基类引用转换为派生类引用该怎么办呢?在 C++ 中是通过 dynamic_cast 来进行向下转换,在下一节我们来实现它。