【问题标题】:How to remove (non-intrusive) smart pointers from a cache when there are no more references?当没有更多引用时,如何从缓存中删除(非侵入式)智能指针?
【发布时间】:2012-01-16 19:08:45
【问题描述】:

由于我的菜鸟名声,我无法回复这个Thread,特别是接受的答案:

我从未使用过 boost::intrusive 智能指针,但如果你要使用 shared_ptr 智能指针,你可以使用weak_ptr 对象作为缓存。

当系统决定释放它们的内存时,这些weak_ptr指针不计为引用,但只要对象尚未被删除,就可以用于检索shared_ptr。

这当然是一个直观的想法,但是,C++ 标准不支持weak_ptrs 的比较,因此它不能用作关联容器的键。这可以通过为weak_ptrs实现一个比较运算符来规避:

template<class Ty1, class Ty2>
    bool operator<(
        const weak_ptr<Ty1>& _Left,
        const weak_ptr<Ty2>& _Right
    );

这个解决方案的问题在于

(1) 比较运算符必须为每个比较获取所有权(即从weak_ptr refs 创建shared_ptrs)

(2)当最后一个管理资源的shared_ptr被销毁时,weak_ptr并没有从缓存中擦除,而是一个过期的weak_ptr被保存在缓存中。

对于 (2),我们可以提供一个自定义析构函数 (DeleteThread),但是,这需要再次从要删除的 T* 创建一个weak_ptr,然后可以使用它从缓存。

我的问题是,是否有更好的使用智能指针的缓存方法(我使用的是 VC100 编译器,没有提升),还是我根本不明白?

干杯,丹尼尔

【问题讨论】:

  • 您不能更改任何基于密钥的容器的密钥!!!它必须是不可变的!您可以有一些自定义的 delete-the-last 伸出并从您的容器中删除条目。但是在容器中更改键值是一个巨大的禁忌。
  • 这正是问题所在,我需要提供一个自定义删除器来从缓存中删除对象以避免缓存中的指针过期。还是您说的更笼统,因为引用计数正在改变?

标签: c++ caching shared-ptr smart-pointers weak-ptr


【解决方案1】:

您想要实现的可能解决方案可能是

假设T 是您的对象,shared_ptr&lt;T&gt; 是您的共享指针

  1. 缓存中只有常规的T*
  2. 为您的shared_ptr&lt;T&gt; 自定义删除器
  3. 让您的自定义删除器在删除时从缓存中删除您的 T*

这样缓存不会增加shared_ptr&lt;T&gt; 的引用计数,而是会在引用计数达到 0 时得到通知。

struct Obj{};

struct Deleter
{
    std::set<Obj*>& mSet;
    Deleter( std::set<Obj*>& setIn  )
        : mSet(setIn) {}

    void operator()( Obj* pToDelete )
    {
        mSet.erase( pToDelete );
        delete pToDelete;
    }
};

int main ()
{

    std::set< Obj* > mySet;
    Deleter d(mySet);
    std::shared_ptr<Obj> obj1 = std::shared_ptr<Obj>( new Obj() , d );
    mySet.insert( obj1.get() );
    std::shared_ptr<Obj> obj2 = std::shared_ptr<Obj>( new Obj() , d );
    mySet.insert( obj2.get() );

    //Here set should have two elements
    obj1 = 0;
    //Here set will only have one element

    return 42;
}

【讨论】:

  • 感谢 parapura,解决方案很好,也是我想要的。我认为你有一个小错误:std::set&lt;Obj*&gt; mSet; 应该是一个参考。
  • @Thanks....当然,我很着急我还没有完全测试它...编辑并纠正了我的错误。我也不确定 Deleter functor 签名。似乎在 VS 2011 下工作
  • @dawn 如果它解决了你的问题,你能接受它作为答案吗?
  • 有趣...但是错误CacheFactory 之间存在细微差别。你的实现很好对于一个 Factory。但是,Cache 应该能够多次返回同一个对象,并且无法从原始T* 创建shared_ptr&lt;T&gt; 的副本,所以你被卡住了......(和创建一个新的shared_ptr&lt;T&gt; 是通往地狱的最短路径)。
  • @MatthieuM。如果也没有排除 boost - by this,这将是微不足道的(至少在单线程情况下)。据我所知,std::shared_ptr 中没有等价物。
【解决方案2】:

问题是,你的Cache 没有被缓存的对象本身寻址,否则它几乎没用。

Cache 的想法是避免一些计算,因此索引将是计算的参数,如果已经存在,它将直接映射到结果。

现在,您实际上可能需要第二个索引来从缓存中删除对象,但这不是强制性的。当然还有其他可用的策略。

如果您真的想在应用程序的其他任何地方没有使用对象时立即从缓存中删除它们,那么您可以有效地使用二级索引。虽然这里的想法是根据T*而不是weak_ptr&lt;T&gt;进行索引,但要保留weak_ptr&lt;T&gt;,否则您无法在相同的引用计数上创建新的shared_ptr

确切的结构取决于计算的参数是否难以在事后重新计算,如果是,一个简单的解决方案是:

template <typename K, typename V>
class Cache: boost::enable_shared_from_this<Cache>
{
  typedef std::map<K, boost::weak_ptr<V>> KeyValueMap;
  typedef std::map<V*, KeyValueMap::iterator> DeleterMap;

  struct Deleter {
    Deleter(boost::weak_ptr<Cache> c): _cache(c) {}

    void operator()(V* v) {
      boost::shared_ptr<Cache> cache = _cache.lock();
      if (cache.get() == 0) { delete v; return; }

      DeleterMap::iterator it = _cache.delmap.find(v);
      _cache.key2val.erase(it->second);
      _delmap.erase(it);
      delete v;
    }

    boost::weak_ptr<Cache> _cache;
  }; // Deleter

public:
  size_t size() const { return _key2val.size(); }

  boost::shared_ptr<V> get(K const& k) const {
    KeyValueMap::const_iterator it = _key2val.find(k);
    if (it != _key2val.end()) { return boost::shared_ptr<V>(it->second); }

    // need to create it
    boost::shared_ptr<V> ptr(new_value(k),
        Deleter(boost::shared_from_this()));

    KeyValueMap::iterator kv = _key2val.insert(std::make_pair(k, ptr)).first;
    _delmap.insert(std::make_pair(ptr.get(), kv));

    return ptr;
  }


private:
  mutable KeyValueMap _key2val;
  mutable DeleterMap _delmap;
};

注意特别困难:指针可能比Cache 寿命更长,所以我们需要一些技巧......

根据您的信息,虽然它看起来可行,但我对这段代码完全没有信心:未经测试,未经证实,bla,bla ;)

【讨论】:

  • 解决方案很好,特别是 DeleterMap 是我正在寻找的。但是我不同意缓存对象不是缓存的关键这一点:想法是拥有一个指针缓存,以确保在取消引用时没有两个相等的指针(使用取消引用较少的比较进行排序)。这使我可以跨多个对象重用(不可变)对象指针以安全内存。不过,我可以简单地使用您更通用的方法 typedef std::map&lt;K, boost::weak_ptr&lt;V&gt;&gt; KeyValueMap; 并将其替换为 std::set
  • 我也没有测试过,但认为if (cache.get() == 0) { delete v; }应该是if (_cache.get() == 0) { delete v; return; }
  • @dawn:关于你的特殊情况:确实,适应你自己的需要:) 关于你的评论...... doh!谢谢,我修好了:)
  • "if (cache.get() == 0)" 绝不是真的! "boost::shared_ptr&lt;Cache&gt; cache(_cache);" "当 r.expired() 时抛出:bad_weak_ptr。"
  • @curiousguy:感谢缓存这个,我本来打算使用lock...有人会想知道为什么两者没有相同的行为:x
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-09-10
  • 1970-01-01
  • 1970-01-01
  • 2010-11-18
相关资源
最近更新 更多