最简单的boost::intrusive_ptr
(或自制版本)如下所示:
template<typename T>
class intrusive_ptr {
public:
intrusive_ptr(T* ptr) : ptr_(ptr)
{
if (ptr_) {
intrusive_ptr_add_ref(ptr_);
}
}
intrusive_ptr(const intrusive_ptr& that) : ptr_(that.ptr_)
{
if (ptr_) {
intrusive_ptr_add_ref(ptr_);
}
}
~intrusive_ptr()
{
if (ptr_) {
intrusive_ptr_release(ptr_);
}
}
// ...
private:
ptr_;
};
用法:
class Foo {
public:
// ...
private:
std::size_t refcount_;
friend void intrusive_ptr_add_ref(const Foo* p)
{
++p->refcount_;
}
friend void intrusive_ptr_release(const Foo* p)
{
if (--p->refcount_ == 0) { // line 1
delete p; // line 2
}
}
};
intrusive_ptr<Foo> p(new Foo);
显然,Foo
现已实施,intrusive_ptr<Foo>
不是线程安全的。简单地将Foo::refcount_
的类型更改为std::atomic<std::size_t>
也不够,因为当一个线程位于第1行和第2行之间时,另一个线程可能会尝试增加引用计数。
所以我的问题是:是否有可能使intrusive_ptr
线程安全,理想情况下不使用互斥体等重型机制?
答案 0 :(得分:2)
所以我的问题是:是否有可能使intrusive_ptr线程安全, 理想情况下,不使用像互斥体这样的重型机制?
是。将计数器更改为std :: atomic就足够了,因为如果线程A将计数器值减少到零,那么可以保证没有其他的intrusive_ptr&lt;&gt;对象指向对象p。 (因为如果确实存在,则refcount值仍将大于零)。
所以你担心的竞争条件不可能发生。 (好吧,如果其他一些线程解除引用对象p的原始指针,而不是持有一个intrusive_ptr,可能会发生这种情况,但在这种情况下,所有的赌注都会被关闭,因为程序是错误的)
答案 1 :(得分:1)
您可以选择检测指针盗窃:
#include <cstdint>
#include <atomic>
#include <cassert>
#include <stdexcept>
struct allow_zero_access {};
template<typename T>
class intrusive_ptr {
public:
intrusive_ptr(T* ptr, allow_zero_access)
: ptr_(ptr)
{
assert(ptr);
intrusive_ptr_init_ref(ptr_, allow_zero_access());
}
intrusive_ptr(T* ptr) : ptr_(ptr)
{
if (ptr_) {
intrusive_ptr_add_ref(ptr_);
}
}
intrusive_ptr(const intrusive_ptr& that) : ptr_(that.ptr_)
{
if (ptr_) {
intrusive_ptr_add_ref(ptr_);
}
}
intrusive_ptr& operator=(const intrusive_ptr& that)
{
intrusive_ptr tmp(that);
std::swap(this->ptr_, tmp.ptr_);
return *this;
}
~intrusive_ptr()
{
if (ptr_) {
intrusive_ptr_release(ptr_);
}
}
// ...
private:
T* ptr_;
};
template<class T>
struct enable_mt_intrusive_pointer
{
private:
friend void intrusive_ptr_init_ref(const enable_mt_intrusive_pointer* p, allow_zero_access)
{
assert(p);
if (p->_refcount.fetch_add(1) != 0) {
throw std::logic_error("stealing someone's pointer!");
}
}
friend void intrusive_ptr_add_ref(const enable_mt_intrusive_pointer* p, bool first_access = false)
{
assert(p);
if (p->_refcount.fetch_add(1) == 0 && !first_access) {
throw std::logic_error("resurrecting a zombie");
}
}
friend void intrusive_ptr_release(const enable_mt_intrusive_pointer* p)
{
assert(p);
switch(p->_refcount.fetch_sub(1)) {
case 1:
delete p;
break;
case 0:
throw std::logic_error("already deleted");
break;
default:
;
}
}
mutable std::atomic<std::size_t> _refcount { 0 };
};
template<class T, class...Args>
intrusive_ptr<T> make_intrusive_ptr(Args&&...args)
{
return { new T(std::forward<Args>(args)...),
allow_zero_access() };
}
class Foo : public enable_mt_intrusive_pointer<Foo>
{
public:
// ...
};
int main()
{
auto p = make_intrusive_ptr<Foo>();
}
但是,实际上很少有理由在c ++程序中使用intrusive_ptr。即使在与外部c库接口时,其他侵入式指针也可以用自定义删除器封装在std :: shared_ptr中。