Как удалить (неинтрузивные) интеллектуальные указатели из кеша, когда больше нет ссылок?

Из-за моей репутации noob, я не могу ответить на этот Thread, в частности, принятый ответ:

Я никогда не использовал boost:: intrusive smart pointers, но если бы вы использовали интеллектуальные указатели shared_ptr, вы могли бы использовать объекты weak_ptr для вашего кеша.

Эти указатели weak_ptr не считаются ссылкой, когда система решает освободить их память, но может использоваться для извлечения shared_ptr, пока объект еще не удален.

Это, конечно, интуитивная идея, однако стандарт С++ не поддерживает сравнение weak_ptrs, поэтому он не может использоваться как ключ для ассоциативных контейнеров. Это можно обойти, реализовав оператор сравнения для weak_ptrs:

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

Проблема с этим решением состоит в том, что

(1) оператор сравнения должен получить право собственности на каждое сравнение (т.е. создавая shared_ptrs из ссылок weak_ptr)

(2) weak_ptr не удаляется из кеша, когда последний shared_ptr, который управляет ресурсом, уничтожается, но с истекшим значением weak_ptr хранится в кеше.

Для (2) мы могли бы предоставить пользовательский деструктор (DeleteThread), однако для этого потребуется снова создать weak_ptr из T *, который должен удалить, который затем можно использовать для удаления слабой_ptr из кеша.

Мой вопрос будет, если есть лучший подход к кешу с помощью интеллектуальных указателей (я использую компилятор VC100, без повышения), или просто не получаю его?

Приветствия, Даниэль

Ответ 1

Дело в том, что ваш Cache не обрабатывается самим объектом, который был кеширован, иначе это было бы бесполезно.

Идея Cache заключается в том, чтобы избежать некоторых вычислений, поэтому индекс будет параметром вычисления, который будет непосредственно отображать результат, если он уже присутствует.

Теперь вам может понадобиться второй указатель для удаления объектов из кеша, но это необязательно. Есть, конечно, другие доступные стратегии.

Если вы действительно хотите удалить объекты из кеша, как только они не будут использоваться где-либо еще в приложении, то, фактически, вы можете использовать вторичный индекс. Идея здесь состоит в том, чтобы индексировать в соответствии с T*, а не weak_ptr<T>, но поддерживать weak_ptr<T> вокруг, потому что иначе вы не можете создать новый 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, поэтому нам нужен какой-то трюк здесь...

И для вашей информации, хотя это кажется выполнимым, я совсем не уверен в этом коде: untested, unproven, bla, bla;)

Ответ 2

Возможное решение для того, что вы хотите достичь, может быть

Допустим, что T - ваш объект, а shared_ptr<T> - ваш общий ptr

  • В вашем кеше есть только T*.
  • У вас есть пользовательский отладчик для shared_ptr<T>
  • Устраните свой пользовательский отладчик T* из кеша при удалении.

Таким образом, кеш не увеличивает счетчик ссылок shared_ptr<T>, но уведомляется, когда количество ссылок достигает 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;
}