缓存/内存与对象生存期

时间:2019-03-05 01:44:57

标签: rust

我的程序被构造为一系列函数调用,以建立结果值-每个函数将返回的值返回(移动)到其调用方。这是一个简化的版本:

struct Value {}

struct ValueBuilder {}

impl ValueBuilder {

    pub fn do_things_with_value(&mut self, v : &Value) {
        // expensive computations
    }

    pub fn make_value(&self) -> Value {
        Value {}
    }

    pub fn f(&mut self) -> Value {
        let v = self.make_value();
        self.do_things_with_value(&v);
        v
    }

    pub fn g(&mut self) -> Value {
        let v = self.f();
        self.do_things_with_value(&v);
        v
    }
}

play.rust-lang version

想象一下,在它们之间以及上面,还有更多类似于f和g的函数。您可以看到do_things_with_value被两次调用且具有相同的值。我想缓存/存储此调用,以便在下面的示例中,“昂贵的计算”仅执行一次。这是我的尝试(显然不正确):

#[derive(PartialEq)]
struct Value {}

struct ValueBuilder<'a> {
    seen_values: Vec<&'a Value>,
}

impl<'a> ValueBuilder<'a> {
    pub fn do_things_with_value(&mut self, v: &'a Value) {
        if self.seen_values.iter().any(|x| **x == *v) {
            return;
        }
        self.seen_values.push(v)
        // expensive computations
    }

    pub fn make_value(&self) -> Value {
        Value {}
    }

    pub fn f(&mut self) -> Value {
        let v = self.make_value();
        self.do_things_with_value(&v); // error: `v` does not live long enough
        v
    }

    pub fn g(&mut self) -> Value {
        let v = self.f();
        self.do_things_with_value(&v);
        v
    }
}

play.rust-lang version

我理解编译器为什么这样做-在这种情况下,碰巧在两次调用do_things_with_value之间没有删除v,但不能保证不会删除它,而取消引用它会导致崩溃程序。

构造此程序的更好方法是什么?假设:

  • 克隆和存储Values很昂贵,我们负担不起seen_values保留我们见过的所有内容的副本
  • 我们也无法重构代码/ Value对象以携带其他数据(例如,表示是否使用此值进行了昂贵的计算的布尔值)。它需要依靠使用PartialEq
  • 比较值

1 个答案:

答案 0 :(得分:3)

如果您需要在程序的不同位置保留相同的值,则最容易复制或克隆它。

但是,如果由于克隆成本太高而无法克隆,则将值包装在Rc中。那是一个引用计数的智能指针,它允许对其内容进行共享所有权。克隆而不复制所包含的值相对便宜。

请注意,仅将Rc<Value>存储在seen_values中将至少在价值构建者有效期内保持所有价值不变。您可以通过存储Weak个引用来避免这种情况。

use std::rc::{Rc, Weak};

#[derive(PartialEq)]
struct Value {}

struct ValueBuilder {
    seen_values: Vec<Weak<Value>>,
}

impl ValueBuilder {
    pub fn do_things_with_value(&mut self, v: &Rc<Value>) {
        if self
            .seen_values
            .iter()
            .any(|x| x.upgrade().as_ref() == Some(v))
        {
            return;
        }
        self.seen_values.push(Rc::downgrade(v))
        // expensive computations
    }

    pub fn make_value(&self) -> Rc<Value> {
        Rc::new(Value {})
    }

    pub fn f(&mut self) -> Rc<Value> {
        let v = self.make_value();
        self.do_things_with_value(&v);
        v
    }

    pub fn g(&mut self) -> Rc<Value> {
        let v = self.f();
        self.do_things_with_value(&v);
        v
    }
}

函数Rc<Value>的链中正在使用do_things()时,将记住该值并跳过计算。如果某个值变为未使用的值(所有引用均被删除)并随后再次创建,则do_things()将重复计算。