【问题标题】:How to remove smart pointers from a cache when there are no more references?没有更多引用时如何从缓存中删除智能指针?
【发布时间】:2010-03-05 12:01:44
【问题描述】:

我一直在尝试使用智能指针来升级现有应用程序,并且我正在尝试克服一个难题。在我的应用程序中,我有一个对象缓存,例如让我们称它们为书籍。现在这个图书缓存是由 ID 请求的,如果它们在缓存中,则返回它们,如果不是,则从外部系统请求对象(慢操作)并添加到缓存中。一旦在缓存中,可以在应用程序中打开许多窗口,这些窗口中的每一个都可以引用这本书。在以前版本的应用程序中,程序员必须维护 AddRef 和 Release,当每个使用 Book 对象的窗口关闭时,最终的 Release(在缓存管理器上)将从缓存中删除对象并删除该对象。

你可能已经在这里发现了链中的薄弱环节,当然是程序员记得调用 AddRef 和 Release。现在我已经转向智能指针 (boost::intrusive) 我不再需要担心调用 AddRef 和 Release。然而这导致了一个问题,缓存有一个对象的引用,所以当最后一个窗口关闭时,缓存不会被通知没有其他人持有引用。

我的第一个想法是定期遍历缓存并清除引用计数为 1 的对象。我不喜欢这个主意,因为它是一个 Order 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

干杯 丰富

【问题讨论】:

  • 您应该使BaseObject 不可复制(通过声明但不定义私有复制构造函数和赋值运算符),或以某种方式使其可正确复制。同样,Book 具有危险的复制语义,最好通过使用 std::string 而不是手动管理的数组来解决。

标签: c++ caching pointers boost smart-pointers


【解决方案1】:

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

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

【讨论】:

  • 我必须使用侵入式指针,因为代码库非常庞大且相互关联。我无法将基本类型更改为智能指针。我还必须能够将现有指针更改为智能指针并保持引用计数。侵入式指针可以做的事情。
  • 使用共享指针,您根本不需要基类型,这将是解开代码库的好一步。您还将获得线程安全和异常安全。
  • 共享指针的最大问题是我需要适当地使用共享类型。我也必须能够使用基本的 c 指针,要转换的代码太多了。
【解决方案2】:

您可以使用boost shared_ptr。有了这个,您可以提供一个自定义删除器(请参阅this SO 线程了解如何执行此操作)。在那个自定义删除器中,您知道您已达到最后一个引用计数。您现在可以从缓存中删除指针。

【讨论】:

  • 你能添加自定义删除器到信任指针吗? (我知道我可以通过编写我自己的侵入式指针版本)
  • 只要指针存在缓存中,引用计数就永远不会降为0,也不会调用自定义删除器。
  • @visitor:是的,你是对的,但是 OP 可以重新设计缓存机制。让缓存包含原始指针,这些指针将在被 GUI 窗口访问之前由 shared_ptr 包装。当所有指针都超出范围时,用户可以删除缓存条目,如果需要也可以删除。
  • @Abhay:更好的是,存储弱指针(正如 Timbo 建议的那样)以避免悬空引用。
  • @Rich:Release 函数本质上是一个自定义删除器。
【解决方案3】:

您需要在缓存中保存weak pointers 而不是shared_ptr。

【讨论】:

  • 请注意weak_ptr 根本不是“指针”。它没有“指针”语义,而是“弱引用”语义。
【解决方案4】:

您可以考虑为您的缓存类编写一个 intrusive_weak_ptr。您仍然需要不时地清理缓存中过期的弱指针,但这并不像清理实际缓存的对象那么重要。

http://lists.boost.org/boost-users/2008/08/39563.php 是一个发布到 boost 邮件列表的实现。它不是线程安全的,但它可能对你有用。

【讨论】:

    猜你喜欢
    • 2012-01-16
    • 2012-09-10
    • 1970-01-01
    • 1970-01-01
    • 2013-08-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-05-28
    相关资源
    最近更新 更多