锁定shared_ptr

Ear*_*ine 11 c++ shared-ptr c++11

我有一个共享对象需要发送到系统API并稍后将其解压缩.系统API仅接收void*.我不能使用shared_ptr :: get(),因为它不会增加引用计数,并且可以在从系统API中提取之前由其他线程释放.发送新的shared_ptr*将起作用,但涉及额外的堆分配.

一种方法是让对象派生自enable_shared_from_this.但是,因为此类模板仅拥有weak_ptr,所以仅保持释放对象是不够的.

所以我的解决方案如下所示:

class MyClass:public enable_shared_from_this<MyClass> {
private:
    shared_ptr<MyClass> m_this;
public:
    void *lock(){
        m_this=shared_from_this();
        return this;
    }
    static shared_ptr<MyClass> unlock(void *p){
        auto pthis = static_cast<MyClass *>(p);
        return move(pthis->m_this);
    }
/* ... */
}

/* ... */
autp pobj = make_shared<MyObject>(...);
/* ... */
system_api_send_obj(pobj->lock());
/* ... */
auto punlocked = MyClass::unlock(system_api_reveive_obj());
Run Code Online (Sandbox Code Playgroud)

有没有更简单的方法来做到这一点?

这个解决方案的缺点:

  • 除了基类中shared_ptr<MyClass>的a 之外,它还需要在MyClass对象布局中添加一个.weak_ptrenable_shared_from_this

  • 正如我在评论中提到的,访问lock()unlock()同时不安全.

  • 最糟糕的是,这个解决方案只能lock()在呼叫之前支持一次unlock().如果要将同一对象用于多个系统API调用,则必须实现其他引用计数.

如果我们有另一个enable_lockable_shared_from_this类,那将是更大的:

class MyClass:public enable_lockable_shared_from_this<MyClass> {
/* ... */
}

/* ... */
autp pobj = make_shared<MyObject>(...);
/* ... */
system_api_send_obj(pobj.lock());
/* ... */
auto punlocked = unlock_shared<MyClass>(system_api_reveive_obj());
Run Code Online (Sandbox Code Playgroud)

和实现enable_lockable_shared_from_this类似enable_shared_from_this,唯一的区别是它实现lock()了一个辅助函数unlock_shared.调用这些函数只能显式增加和减少use_count().这将是理想的解决方案,因为:

  • 它消除了额外的空间成本

  • 它重用了shared_ptr现有的工具来保证并发安全.

  • 此解决方案的最佳之处在于它可以lock()无缝支持多个调用.

然而,唯一最大的缺点是:目前还没有!

更新:

这个问题至少有两个答案涉及指针容器.请将这些解决方案与以下内容进

class MyClass:public enable_shared_from_this<MyClass> {
private:
    shared_ptr<MyClass> m_this;
    mutex this_lock; //not necessory for single threading environment
    int lock_count;
public:
    void *lock(){
        lock_guard lck(this_lock); //not necessory for single threading environment
        if(!lock_count==0)
            m_this=shared_from_this();
        return this;
    }
    static shared_ptr<MyClass> unlock(void *p){
        lock_guard lck(this_lock); //not necessory for single threading environment
        auto pthis = static_cast<MyClass *>(p);
        if(--lock_count>0)
            return pthis->m_this;
        else {
            lock_count=0;
            return move(pthis->m_this); //returns nullptr if not previously locked
        }
    }
/* ... */
}

/* ... */
autp pobj = make_shared<MyObject>(...);
/* ... */
system_api_send_obj(pobj->lock());
/* ... */
auto punlocked = MyClass::unlock(system_api_reveive_obj());
Run Code Online (Sandbox Code Playgroud)

这绝对是O(1)vs O(n)(空间;时间是O(log(n))或类似,但绝对大于O(1))游戏.

Ear*_*ine 3

通过调整之前的答案,我最终得到了以下解决方案:

//A wrapper class allowing you to control the object lifetime
//explicitly.
//
template<typename T> class life_manager{
public:
    //Prevent polymorphic types for object slicing issue.
    //To use with polymorphic class, you need to create
    //a container type for storage, and then use that type.
    static_assert(!std::is_polymorphic<T>::value, 
        "Use on polymorphic types is prohibited.");

    //Example for support of single variable constructor
    //Can be extented to support any number of parameters
    //by using varidict template.
    template<typename V> static void ReConstruct(const T &p, V &&v){ 
        new (const_cast<T *>(&p))T(std::forward<V>(v));
    }

    static void RawCopy(T &target, const T &source){
        *internal_cast(&target) = *internal_cast(&source);
    }
private:
    //The standard said that reterinterpret_cast<T *>(p) is the same as 
    //static_cast<T *>(static_cast<void *>(p)) only when T has standard layout.
    //
    //Unfortunately shared_ptr do not.
    static struct { char _unnamed[sizeof(T)]; } *internal_cast(const T *p){
        typedef decltype(internal_cast(nullptr)) raw;
        return static_cast<raw>(static_cast<void *>(const_cast<T *>(p)));
    }
};

//Construct a new instance of shared_ptr will increase the reference
//count. The original pointer was overridden, so its destructor will
//not run, which keeps the increased reference count after the call.
template<typename T> void lock_shared(const std::shared_ptr<T> &p){
    life_manager<shared_ptr<T> >::ReConstruct(p, std::shared_ptr<T>(p));
}

//RawCopy do bit-by-bit copying, bypassing the copy constructor
//so the reference count will not increase. This function copies
//the shared_ptr to a temporary, and so it will be destructed and
//have the reference count decreased after the call.
template<typename T> void unlock_shared(const std::shared_ptr<T> &p){
    life_manager<shared_ptr<T> >::RawCopy(std::shared_ptr<T>(), p);
}
Run Code Online (Sandbox Code Playgroud)

然而,这实际上与我之前的版本相同。我所做的唯一一件事就是创建一个更通用的解决方案来显式控制对象生命周期。

根据标准(5.2.9.13),static_cast序列肯定是明确定义的。此外,“原始”复制行为可能是未定义的,但我们明确要求这样做,因此用户必须在使用此功能之前检查系统兼容性。

此外,实际上RawCopy()本示例中只需要这些。的介绍ReConstruct只是为了一般目的。