Как удалить (не навязчивые) умные указатели из кэша, когда больше нет ссылок? - PullRequest
14 голосов
/ 09 декабря 2011

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

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

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

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

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

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

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

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

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

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

Ура, Даниэль

Ответы [ 2 ]

4 голосов
/ 09 декабря 2011

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

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

.
  1. Только регулярно T* в вашем кэше.
  2. У вас есть пользовательский удалитель для shared_ptr<T>
  3. Ваш пользователь удалит ваш файл 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;
}
2 голосов
/ 09 декабря 2011

Дело в том, что ваш 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, поэтому нам нужен какой-то трюк здесь ...

И к вашему сведению, хотя это кажется возможным, я совсем не уверен в этом коде: непроверенный, недоказанный, бла, бла;)

...