在 C++ 中我们可以用基类的指针来持有派生来对象,如下:

Base* p = new Derive1(1, 2, 3);

但在 Rust 中,原始指针的使用是受限的,我们需要用智能指针。下面我们来尝试一下:

let b: Box<Base> = Box::<Derive1>::new(Derive1::new(1, 2, 3));
let b: Box<Base> = Box::<Base>::new(Derive1::new(1, 2, 3));

并不顺利,编译器认为 Box<Base> 和 Box<Derive1> 以及 Base 和 Derive1 是不同的类型,因而拒绝赋值。

   --> class_impl/src/lib.rs:392:24
    |
392 |     let b: Box<Base> = Box::<Derive1>::new(Derive1::new(1, 2, 3));
    |            ---------   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected struct `Base`, found struct `Derive1`
    |            |
    |            expected due to this
error[E0308]: mismatched types
    = note: expected struct `Box<Base>`
               found struct `Box<Derive1>`
   --> class_impl/src/lib.rs:393:41
393 |     let b: Box<Base> = Box::<Base>::new(Derive1::new(1, 2, 3));
    |                                         ^^^^^^^^^^^^^^^^^^^^^ expected struct `Base`, found struct `Derive1`

我们再尝试一下 dyn 关键字:

let b: Box<dyn Base> = Box::<Derive1>::new(Derive1::new(1, 2, 3));
let b: Box<dyn Base> = Box::<dyn Base>::new(Derive1::new(1, 2, 3));

仍然被拒绝。

error[E0404]: expected trait, found struct `Base`
   --> class_impl/src/lib.rs:392:20
392 |     let b: Box<dyn Base> = Box::<Derive1>::new(Derive1::new(1, 2, 3));
    |                    ^^^^ not a trait
error[E0404]: expected trait, found struct `Base`
   --> class_impl/src/lib.rs:393:20
393 |     let b: Box<dyn Base> = Box::<dyn Base>::new(Derive1::new(1, 2, 3));
    |                    ^^^^ not a trait
error[E0404]: expected trait, found struct `Base`
   --> class_impl/src/lib.rs:393:38
393 |     let b: Box<dyn Base> = Box::<dyn Base>::new(Derive1::new(1, 2, 3));
    |                                      ^^^^ not a trait

既然编译器不支持,我们只好自己动手了。

    let d = Box::new(Derive1::new(1, 2, 3));
    let p = Box::into_raw(d) as *mut Base;
    let b = unsafe { Box::<Base>::from_raw(p) };

这样虽然可以达到目的,但太过于繁琐了,而且还使用了不安全的代码。我们将它写成一个安全的函数:

fn make_box<B, D>(d: D) -> Box<B>
{
    let d = Box::new(d);
    let p = Box::into_raw(d) as *mut B;
    unsafe { Box::<B>::from_raw(p) }
}
let b: Box<Base> = make_box(Derive1::new(1, 2, 3));

如此一来,确实方便了,但同时也带来了安全隐患,我们没有验证类型是否可以转换,也就是说,我们无法阻止用户非法使用 make_box 方法,如下:

let d: Box<Derive1> = make_box(Base::new(1, 2));
let p: Box<i32> = make_box(Vec::<String>::new());

为此我们需要改进 make_box 函数,如下:

fn make_box<B, D>(d: D) -> Box::<B>
where
    D: DerefMut<Target=B>,
{
    let p = Box::new(d);
    let d = unsafe { &mut *Box::into_raw(p) };
    let b = d as &mut B;
    unsafe { Box::<B>::from_raw(b as *mut B) }
}
let b: Box<Base> = make_box(Derive1::new(1, 2, 3));

在这里,我们先将指针转换为引用,虽然用了不安全的代码,但我们知道在这里是安全的,然后通过引用来做类型转换,而这个转换会自动调用 DerefMut::deref_mut(&mut self) 方法,实现安全的类型转换。从而确保整个函数的行为是安全的,最后再将引用转换为指针及智能指针。虽然绕了好大一圈,代码也不少,但只要编译器足够聪明,仍然是 0 开销的。
这回我们不用担心非法的调用了,但新的问题又出现了。

let b: Box<Base> = make_box(Derive2::new(1, 2, 3));

因为 Base 不是 Derive2 的直接基类,再常规的代码中,因为 Derive2 as Derive1 as Base 这样的路径存在,我们可以直接 Dervie2 as Base,但是在 Rust 模板中,这样行不通,除非我们修改函数原型,如下:

fn make_box<B, D, D1>(d: D) -> Box::<B>
where
    D: DerefMut<Target=D1>,
    D1: DerefMut<Target=B>,

这样就能够支持孙类指针赋值给基类,但又不支持子类的指针。
到这里,想要一个十全十美的方案似乎不太可能了。下面有几个不完美的方案
方案一,改用 AsMut:

fn make_box<B, D>(d: D) -> Box::<B>
where
    D: AsMut<B>,
{
    let p = Box::new(d);
    let d = unsafe { &mut *Box::into_raw(p) };
    let b = d.as_mut();
    unsafe { Box::<B>::from_raw(b as *mut B) }
}
impl AsMut<Base> for Base
{ fn as_mut(&mut self) -> &mut Base { self } }
impl AsMut<Base> for Derive1
{ fn as_mut(&mut self) -> &mut Base { unsafe { reinterpret_cast(self) } } }
impl AsMut<Derive1> for Derive1
{ fn as_mut(&mut self) -> &mut Derive1 { self } }
impl AsMut<Base> for Derive2
{ fn as_mut(&mut self) -> &mut Base { unsafe { reinterpret_cast(self) } } }
impl AsMut<Derive1> for Derive2
{ fn as_mut(&mut self) -> &mut Derive1 { unsafe { reinterpret_cast(self) } } }
impl AsMut<Derive2> for Derive2
{ fn as_mut(&mut self) -> &mut Derive2 { self } }

代价就是要为每一个基类及自身都实现 AsMut,随着类的继承层次增加,要实现的 AsMut 也会越来越多。
方案二,改用宏:

macro_rules! make_box
{
    ($expr:expr) => { Box::new($expr) };
    ($name:ident $expr:expr) =>
    {
        {
            let p = Box::new($expr);
            let d = unsafe { &mut *Box::into_raw(p) };
            let b = d as &mut $name;
            unsafe { Box::<$name>::from_raw(b as *mut $name) }
        }
    };
}
let mut v = Vec::<Box<Base>>::new();
v.push(make_box!(Base::new(1, 2)));
v.push(make_box!(Base Derive1::new(1, 2, 3)));
v.push(make_box!(Base Derive2::new(1, 2, 3)));

宏会在调用处展开,没有了模板参数的约束条件,因此无论是 Derive1 还是 Derive2 都可以直接 as Base,但不影响安全性,因为非法的 as 编译器会拒绝。
方案三,通过 typeinfo 验证:

fn make_box<B, D>(d: D) -> Box::<B>
where
    B: TypeInfoTrait,
    D: TypeInfoTrait,
{
    if B::get_typeinfo().is_base_of(D::get_typeinfo())
    {
        let b = Box::new(d);
        let p = Box::into_raw(b) as *mut B;
        unsafe { Box::<B>::from_raw(p) }
    }
    else
    {
        panic!("invalid cast.");
    }
}

通过 typeinfo 可以更准确的判断两个类型的指针是否可以赋值,因此不需要将指针转换为引用再做 as 的操作来验证,但缺点是,判断发生于运行时,而不是编译时,而且每次转换都有了代价。
我们总算是解决了派生类指针赋值给基类指针的问题,下面开始验证析构函数:

impl Drop for Base
{
    fn drop(&mut self){ println!("Base::drop."); }
}
impl Drop for Derive1
{
    fn drop(&mut self){ println!("Derive1::drop."); }
}
impl Drop for Derive2
{
    fn drop(&mut self){ println!("Derive2::drop."); }
}
let b: Box<Base> = make_box(Base::new(1, 2));
let b1: Box<Base> = make_box(Derive1::new(1, 2, 3));
let b2: Box<Base> = make_box(Derive2::new(1, 2, 3));

然而派生类的析构函数并没有被调用:

Base::drop.
Base::drop.
Base::drop.

这并不难理解,在 C++ 中要正确的调用派生类的析构函数也要走虚析构函数的。看来需要我们自己来实现智能指针和虚析构函数了,作为一个 C++ 程序员,这个不难。

标签: Rust 模拟 C++

已有 3 条评论

  1. 怎么收藏这篇文章?

  2. 博主太厉害了!

  3. 哈哈哈,写的太好了https://www.lawjida.com/

添加新评论