【问题标题】:How to use shared_ptr to manage already ref-count managed objects?如何使用 shared_ptr 来管理已经引用计数的托管对象?
【发布时间】:2017-03-10 11:54:35
【问题描述】:

如果一个对象已经被引用计数(如 C 中的 glib),则具有 obj_refobj_unref。我们只有一个像obj *p 这样的指针。

我们如何使用c++的shared_ptr来管理对象,以便我们有一个统一的接口。


好吧,看来很多人误解了我的用意。

这里最大的问题不是删除器。是通知原经理我增加了refcount。

如果我分配或复制,只有std::shared_ptr增加了引用计数,而原来的没有。反正有通知吗? unref 操作也是如此。

【问题讨论】:

标签: c++ c++11 memory


【解决方案1】:

std::shared_ptr 允许您传递一个自定义删除器,该删除器在应销毁拥有的对象时调用。你可以用它来调用obj_unref

obj* p = create_obj();
p->obj_ref();
std::shared_ptr<obj> sp(p, [](auto p) {
        p->obj_unref();
    });
/* use sp normally, obj will be 'obj_unref'ed and deleted when sp goes out of scope */

我不知道 obj 是如何创建的,以及当计数达到 0 时它是否被 obj_unref() 销毁,但我希望你明白我的意思。
这个想法是在开始时只增加一次 objs 内部引用计数,并在最后一个 shared_ptr 被销毁时减少一次。

【讨论】:

  • 该解决方案如何增加 shared_ptr 被复制时的引用计数?
  • 它没有。这个想法是在开始时只增加一次内部obj ref cout,并在最后一个shared_ptr 被销毁时减少一次。
【解决方案2】:

不要试图以某种方式将胶带std::shared_ptr 引用到您的自定义的引用,这不会有好的结果。只需编写一个自定义指针:

struct objPtr {

    objPtr()
    : _ptr{nullptr} { }

    objPtr(obj *ptr)
    : _ptr{ptr} {
        if(_ptr)
            _ptr->obj_ref();
    }

    ~objPtr() {
        if(_ptr)
            _ptr->obj_unref();
    }

    objPtr(objPtr const &orig)
    : objPtr{orig._ptr} { }

    objPtr &operator = (objPtr const &orig) {
        obj *const oPtr = std::exchange(_ptr, orig._ptr);

        _ptr->obj_ref();
        oPtr->obj_unref();

        return *this;
    }

    obj &operator * () { return *_ptr; }
    obj const &operator * () const { return *_ptr; }

    obj *operator -> () { return _ptr; }
    obj const *operator -> () const { return _ptr; }

    operator bool() const { return _ptr; }
    bool operator ! () const { return !_ptr; }

private:
    obj *_ptr;
};

如果您愿意,可以添加移动构造和分配。

【讨论】:

    【解决方案3】:

    如果您需要shared_ptr,请以unique_ptr 开头。然后建立起来。

    struct cleanup_obj {
      // not called with nullptr:
      void operator()(obj* t)const {
        obj_unref(t);
      }
    };
    using obj_unique_ptr = std::unique_ptr<T, cleanup_obj>;
    using obj_shared_ptr = std::shared_ptr<T>;
    template<class T>
    obj_unique_ptr<T> make_unique_refcount( T* t ) {
      using ptr=obj_unique_ptr<T>;
      if (!t) return ptr();
      obj_ref(t);
      return ptr(t);
    }
    template<class T>
    obj_shared_ptr<T> make_shared_refcount( T* t ) {
      return make_unique_refcount(t); // implicit convert does right thing
    }
    

    我做了什么?

    首先,我写了一个unique_ptr包装器,因为我们还不如完整,它通过unique_ptr->shared_ptr隐式转换解决了shared_ptr的情况。

    对于unique_ptr,我们不得不说我们没有使用默认的对象销毁器。在这种情况下,我们使用了一个无状态函数对象,它知道如何obj_unrefobj*。无状态函数对象保持开销为零。

    对于 null 的情况,我们不先添加引用,因为这很粗鲁。

    对于shared_ptr,我们有一个有效的unique_ptr 使其成为免费功能。 shared_ptr 将愉快地存储 unique_ptr 拥有的驱逐舰功能。不必告诉它它有一个特殊的对象销毁器,因为shared_ptr 类型默认会擦除对象销毁。 (这是因为unique_ptr&lt;T&gt; 对裸指针的开销为零,而shared_ptr&lt;T&gt; 具有不可避免的引用计数块开销;设计人员认为,一旦您拥有该引用计数块,添加类型擦除的销毁函数就不是了真的很贵)。

    请注意,我们的obj_unique_ptr&lt;T&gt; 在裸指针上也是零开销。很多时候,您会想要其中之一而不是共享的。


    现在,如果您愿意,您可以将 obj_unique_ptr 升级为完整的侵入式指针,其开销低于 shared_ptr

     template<class T>
     struct obj_refcount_ptr : obj_unique_ptr<T> // public
     {
       // from unique ptr:
       obj_refcount_ptr(obj_unique_ptr<T> p):obj_unique_ptr<T>(std::move(p)){}
       obj_refcount_ptr& operator=(obj_unique_ptr<T> p){
         static_cast<obj_unique_ptr<T>&>(*this)=std::move(p);
         return *this;
       }
    
       obj_refcount_ptr(obj_refcount_ptr&&)=default;
       obj_refcount_ptr& operator=(obj_refcount_ptr&&)=default;
       obj_refcount_ptr()=default;
    
       obj_refcount_ptr(obj_refcount_ptr const& o):
         obj_refcount_ptr(make_unique_refcount(o.get())
       {}
       obj_refcount_ptr& operator=(obj_refcount_ptr const& o) {
         *this = make_unique_refcount(o.get());
         return *this;
       }
     };
    

    我认为涵盖了它。现在它是一个零开销的引用计数侵入式智能指针。这些侵入式智能指针可以通过隐式转换转换为std::shared_ptr&lt;T&gt;,因为它们仍然是unique_ptrs。他们只是 unique_ptrs 我们教过的模仿自己!

    确实需要从obj_refcount_ptr 转移到shared_ptr。我们可以解决这个问题:

    operator std::shared_ptr<T>() const {
      return obj_refcount_ptr(*this);
    }
    

    创建*thisobj_refcount_ptr 副本并将其移动到shared_ptr。只有一个 add ref 被调用,而 remove ref 仅在 shared_ptr 计数变为零时被调用。


    一般的方法是从最简单的智能指针 (unique_ptr) 开始,把它弄好,然后利用它的实现来得到shared_ptr,最终得到refcount_ptr。我们可以单独测试unique_ptr的实现,它的正确性使得测试更丰富的指针更容易。

    【讨论】:

      【解决方案4】:

      最简单、侵入性最小、破坏可能性最小的方法是简单地为对象编写自己的外观,将底层对象作为私有成员并提供简单的包装器来访问它。

      然后使用std::shared_ptr

      【讨论】:

        【解决方案5】:

        在多个智能指针实现中拥有相同的对象是一个非常糟糕的主意,因为它们的引用计数无法相互了解。一旦引用计数在一个中达到零,即使另一个仍然持有引用,它也会删除该对象。

        如果你真的不得不这样做,你可以使用自定义删除器(什么都不做)来构建你的智能指针,但我真的不推荐这种方法。

        选择一个实现并坚持下去。

        【讨论】:

        • 如果您的指针抽象在保留时调用 obj_ref 并在释放时调用 obj_unref,则可以确定您没有违反引用计数的原始语义..
        猜你喜欢
        • 2012-05-14
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-04-19
        • 1970-01-01
        • 2010-12-28
        • 2016-02-21
        相关资源
        最近更新 更多