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

Я пытался использовать интеллектуальные указатели для обновления существующего приложения, и я пытаюсь решить загадку. В моем приложении у меня есть кеш объектов, например, я могу называть их книгами. Теперь этот кеш книг запрашивается по идентификатору, и если они находятся в кеше, они возвращаются, если нет, объект запрашивается из внешней системы (медленная работа) и добавляется в кеш. Попав в кеш, в приложении можно открыть множество окон, каждое из которых может принимать ссылку на книгу. В предыдущей версии приложения программист должен был поддерживать AddRef и Release, когда каждое окно, использующее объект Book, было закрыто, последний Release (в диспетчере кеша) удалял объект из кеша и удалял объект.

Возможно, вы заметили здесь слабое звено в цепочке, конечно, программист не забыл вызвать AddRef и Release. Теперь я перешел на интеллектуальные указатели (boost :: intrusive). Мне больше не нужно беспокоиться о вызовах AddRef и Release. Однако это приводит к проблеме, кеш имеет ссылку на объект, поэтому, когда последнее окно закрывается, кеш не уведомляется о том, что никто другой не держит ссылку.

Моей первой мыслью было периодически обходить кеш и очищать объекты со счетчиком ссылок, равным единице. Мне не понравилась эта идея, так как это была операция по Приказу N, и я не чувствовал себя правильным. Я придумал систему обратного вызова, которая лучше, но не фантастична. Я включил код для системы обратного вызова, однако мне было интересно, есть ли у кого-нибудь лучший способ сделать это?

class IContainer
{
public:
    virtual void FinalReference(BaseObject *in_obj)=0;
};

class BaseObject 
{
    unsigned int m_ref;

public:
    IContainer *m_container;

    BaseObject() : m_ref(0),m_container(0)
    {
    }

    void AddRef()
    {
        ++m_ref;
    }
    void Release()
    {
        // if we only have one reference left and we have a container
        if( 2 == m_ref && 0 != m_container )
        {
            m_container->FinalReference(this);
        }

        if( 0 == (--m_ref) )
        {
            delete this;
        }
    }
};

class Book : public BaseObject
{
    char *m_name;
public:
    Book()
    {
        m_name = new char[30];
        sprintf_s(m_name,30,"%07d",rand());
    }
    ~Book()
    {
        cout << "Deleting book : " << m_name;
        delete [] m_name;
    }

    const char *Name()
    {
        return m_name;
    }
};

class BookList : public IContainer
{
public:
    set<BookIPtr> m_books;

    void FinalReference(BaseObject *in_obj)
    {
        set<BookIPtr>::iterator it = m_books.find(BookIPtr((Book*)in_obj));
        if( it != m_books.end() )
        {
            in_obj->m_container = 0;
            m_books.erase( it );
        }
    }
};

namespace boost
{
    inline void intrusive_ptr_add_ref(BaseObject *p)
    {
        // increment reference count of object *p
        p->AddRef();
    }
    inline void intrusive_ptr_release(BaseObject *p)
    {
        // decrement reference count, and delete object when reference count reaches 0
        p->Release();
    } 
} // namespace boost

Ура, Рич


person Rich    schedule 05.03.2010    source источник
comment
Вы должны сделать BaseObject некопируемым (объявив, но не определив частный конструктор копирования и оператор присваивания), или сделать его корректно копируемым каким-либо образом. Точно так же Book имеет опасную семантику копирования, которую лучше всего исправить, используя std::string вместо массива, управляемого вручную.   -  person Mike Seymour    schedule 05.03.2010


Ответы (4)


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

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

person Timbo    schedule 05.03.2010
comment
Мне приходится использовать навязчивые указатели, поскольку кодовая база огромна и очень взаимосвязана. Я не могу изменить базовый тип на умный указатель. Я также должен иметь возможность изменить существующий указатель на интеллектуальный указатель и поддерживать счетчик ссылок. То, что могут сделать навязчивые указатели. - person Rich; 05.03.2010
comment
С общими указателями вам вообще не понадобится базовый тип, что станет хорошим шагом на пути к избавлению от запутанного кода. Вы также получите безопасность потоков и исключений. - person Mike Seymour; 05.03.2010
comment
Самая большая проблема с общим указателем заключается в том, что мне нужно будет разрешить использование общего типа. Я также должен уметь использовать базовый указатель c, просто слишком много кода для преобразования. - person Rich; 05.03.2010

Вы можете использовать boost shared_ptr. С его помощью вы можете предоставить настраиваемое средство удаления (см. этот ветку SO о том, как сделай это). И в этом настраиваемом удаленном устройстве вы знаете, что достигли последнего счетчика ссылок. Теперь вы можете удалить указатель из кеша.

person Abhay    schedule 05.03.2010
comment
Можете ли вы добавить пользовательские удалители к навязчивым указателям? (Я знаю, что могу, написав свою собственную версию навязчивых указателей) - person Rich; 05.03.2010
comment
Пока указатель хранится в кеше, счетчик ссылок никогда не упадет до 0, и пользовательское средство удаления не будет вызываться. - person visitor; 05.03.2010
comment
@visitor: Да, вы правы, но OP может изменить механизм кеширования. Пусть кеш будет содержать необработанные указатели, которые будут заключены в shared_ptr перед тем, как к ним будут обращаться окна графического интерфейса. Когда все указатели выходят за пределы области видимости, пользователь может удалить запись кэша, а также удалить ее, если необходимо. - person Abhay; 05.03.2010
comment
@Abhay: еще лучше, храните слабые указатели (как предлагает Тимбо), чтобы избежать висящих ссылок. - person Mike Seymour; 05.03.2010
comment
@Rich: функция Release, по сути, является настраиваемым средством удаления. - person Mike Seymour; 05.03.2010
comment
@Abhay с кастомным удалением? - person curiousguy; 15.12.2011

Вместо этого вам необходимо хранить в кеше слабые указатели. из shared_ptr.

person Tadeusz Kopec    schedule 05.03.2010
comment
Обратите внимание, что weak_ptr вообще не является указателем. Он не имеет семантики указателя, но имеет слабую семантику ссылки. - person curiousguy; 15.12.2011

Вы можете подумать о написании intrusive_weak_ptr для вашего класса кеша. Вам все равно нужно будет время от времени что-то делать, чтобы очищать просроченные слабые указатели в вашем кеше, но это не так важно, как очистка фактических кэшированных объектов.

http://lists.boost.org/boost-users/2008/08/39563.php - это реализация, которая была размещена в списке рассылки boost. Это не потокобезопасно, но может сработать для вас.

person christopher_f    schedule 05.03.2010