【发布时间】:2011-11-09 23:15:33
【问题描述】:
我有一个共享对象需要发送到系统 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());
有没有更简单的方法来做到这一点?
此解决方案的缺点:
除了基类
enable_shared_from_this中的weak_ptr之外,它还需要在MyClass 对象布局中添加一个shared_ptr<MyClass>。正如我在 cmets 中提到的,同时访问
lock()和unlock()是不安全的。最糟糕的是这个解决方案在调用
unlock()之前只能支持一次lock()。如果同一对象要用于多个系统 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());
而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());
这绝对是一个 O(1) vs O(n)(空间;时间是 O(log(n)) 或类似,但绝对大于 O(1))的游戏。
【问题讨论】:
-
“更简单的方法”?该解决方案是否有效?指向 API 函数的
MyClass指针有什么用?基本上,控制system_api_set_key()和system_api_get_key()的人应该保持一个共享指针,以便适当地管理对象的生命周期。 -
这是有效的,因为对象在锁定时拥有对自身的强引用,并且这是一个循环引用,直到 m_this 被释放。 system_api_send_key(对不起,我改了名字)和system_api_receive_key是SYSTEM API,我不能改。
-
我唯一需要提的是,多线程同时访问 m_this 是不安全的。 shared_ptr 保证对引用对象的多线程访问是安全的,但不是 shared_ptr 本身。所以我们需要围绕
lock和unlock函数调用进行一些锁定。 -
某些 API 允许您将 void * 作为“句柄”传递,只有客户端才能理解;系统只是将其存储在某个地方并在将来返回。是的,代码与 C++11 中的
shared_ptr<MyObject> tmp=m_this; m_this.reset(); return tmp;相同。移动构造函数保证上述内容。 -
@Vaughn :通过 PostMessage 和 GetMessage 传递的消息是整数,具体大小与指针大小相同。即,在这种情况下,计划将指针作为整数传递是完全可以的。
标签: c++ c++11 shared-ptr