如何创建一个异构对象集合?

33

我想在 Vec 中使用特质对象。在 C++ 中,我可以创建一个基类 Thing,并从中派生 Monster1Monster2。然后我可以创建一个 std::vector<Thing*>。虽然 Thing 对象需要存储一些数据,比如 x : int, y : int,但派生类需要添加更多的数据。

目前我有类似以下代码:

struct Level {
    // some stuff here
    pub things: Vec<Box<ThingTrait + 'static>>,
}

struct ThingRecord {
    x: i32,
    y: i32,
}

struct Monster1 {
    thing_record: ThingRecord,
    num_arrows: i32,
}

struct Monster2 {
    thing_record: ThingRecord,
    num_fireballs: i32,
}

我定义了一个ThingTrait,其中包含get_thing_record()attack()make_noise()等方法,并为Monster1Monster2实现了它们。


1
如果你的怪物大部分是预先知道的(即你不是创建一个允许每个人创建新怪物的游戏引擎),那么你可以选择使用枚举来工作。 - Paolo Falabella
6
另请参阅 Reddit 上的讨论。(顺便说一句,如果要转贴一个问题,通常最好在它们之间至少加上链接,这样对于有兴趣的人来说就不会错过讨论了。) - huon
好的。我在使用Traits和返回共享数据的方法之间犹豫不决...或者只是为所有内容使用枚举。我认为前者是两种恶中较轻的一种。如果要将继承添加到语言中,它会是什么样子?除此之外,我几乎没有从C++中错过任何东西。真是一股清新的空气。 - stevenkucera
1
Rust博客涵盖了这个主题:无额外开销的抽象:traits——在我看来是一篇非常好的文章。 - legends2k
1个回答

37

特质对象

实现一个异构对象集合(在本例中为向量)最具可扩展性的方式就是使用您已经拥有的方法:

Vec<Box<dyn ThingTrait + 'static>>

虽然有时候你可能需要一个不是 'static 的生命周期,那么你就需要像这样的东西:

Vec<Box<dyn ThingTrait + 'a>>

你也可以拥有一个对特质的引用集合,而不是装箱特质:
Vec<&dyn ThingTrait>

一个例子:
trait ThingTrait {
    fn attack(&self);
}

impl ThingTrait for Monster1 {
    fn attack(&self) {
        println!("monster 1 attacks")
    }
}

impl ThingTrait for Monster2 {
    fn attack(&self) {
        println!("monster 2 attacks")
    }
}

fn main() {
    let m1 = Monster1 {
        thing_record: ThingRecord { x: 42, y: 32 },
        num_arrows: 2,
    };

    let m2 = Monster2 {
        thing_record: ThingRecord { x: 42, y: 32 },
        num_fireballs: 65,
    };

    let things: Vec<Box<dyn ThingTrait>> = vec![Box::new(m1), Box::new(m2)];
}

Box<dyn SomeTrait>Rc<dyn SomeTrait>&dyn SomeTrait等都是特质对象。这些允许在无限数量的类型上实现特质,但代价是需要一定程度的间接性和动态分派。

另请参见:

枚举

正如评论中提到的,如果你有一个固定数量的已知选择,一个不那么开放式的解决方案是使用枚举。这不要求值被Box包装,但它仍然需要一小部分动态分派来决定运行时存在哪个具体枚举变量:

enum Monster {
    One(Monster1),
    Two(Monster2),
}

impl Monster {
    fn attack(&self) {
        match *self {
            Monster::One(_) => println!("monster 1 attacks"),
            Monster::Two(_) => println!("monster 2 attacks"),
        }
    }
}

fn main() {
    let m1 = Monster1 {
        thing_record: ThingRecord { x: 42, y: 32 },
        num_arrows: 2,
    };

    let m2 = Monster2 {
        thing_record: ThingRecord { x: 42, y: 32 },
        num_fireballs: 65,
    };

    let things = vec![Monster::One(m1), Monster::Two(m2)];
}

另请参阅:


4
请注意,枚举类型的大小取决于其最大选项。如果您的 Monster 类型还有一个大数组选项,则您向量中每个 Monster 实例都将占用大数组的内存空间。 - sam

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接