在C ++ 11中,有std::shared_ptr
+ std::weak_ptr
组合。尽管非常有用,但它有一个令人讨厌的问题:你cannot easily construct shared_ptr from a raw pointer。由于这个缺陷,这些智能指针通常变得“病毒”:人们开始完全避免原始指针和引用,并在代码中使用shared_ptr和weak_ptr智能指针。因为没有办法将原始引用传递给期望智能指针的函数。
另一方面,有boost::intrusive_ptr
。它等价于std::shared_ptr
,可以很容易地从原始指针构造,因为引用计数器包含在对象中。不幸的是,它没有weak_ptr伴侣,所以没有办法让你可以检查无效的非拥有引用。事实上,有些人认为weak companion for intrusive_ptr is impossible。
现在,有std::enable_shared_from_this
,embeds a weak_ptr直接进入您的类,因此您可以从指针到对象构造shared_ptr。但是存在一些小的限制(必须至少存在一个shared_ptr),并且它仍然不允许使用明显的语法:std::shared_ptr(pObject)
。
此外,还有一个std::make_shared
,allocates reference counters and the user's object in a single memory chunk。这非常接近intrusive_ptr的概念,但用户的对象可以独立于引用计数块而被销毁。此外,这个概念有一个不可避免的缺点:只有当所有weak_ptr-s都消失时,整个内存块(可能很大)才会被释放。
主要问题是:如何创建一对shared_ptr / weak_ptr,这将有std::shared_ptr
/ std::weak_ptr
和boost::intrusive_ptr
的好处?
特别是:
侵入是可以的,即要求用户从给定的基类继承一次。当对象已经被破坏时保持对象的内存也是可以的。线程安全是非常好的(除非效率太低),但没有它的解决方案也很有趣。可以为每个对象分配几个内存块,但每个对象最多只有一个内存块。
答案 0 :(得分:3)
第1-4和第6点已经由shared_ptr / weak_ptr建模。
第5点毫无意义。如果共享生命周期,则如果存在weak_ptr
但shared_ptr
不存在,则没有有效对象。任何原始指针都是无效指针。对象的生命周期已经结束。对象已不复存在。
weak_ptr
不会使对象保持活动状态,它会使控制块保持活动状态。 shared_ptr
使控制块和受控对象保持活动状态。
如果您不想通过将控制块与受控对象组合来“浪费”内存,请不要调用make_shared
。
如果您不希望shared_ptr<X>
以病毒式传递给函数,请不要传递它。将引用或const引用传递给X
。如果您打算在函数中管理生命周期,则只需在参数列表中提及shared_ptr
。如果您只想对shared_ptr指向的内容执行操作,请传递*p
或*p.get()
并接受[const]引用。
答案 1 :(得分:1)
覆盖对象上的new
以在对象实例之前分配控制块。
这是伪入侵的。由于已知的偏移,可以从原始指针转换为。可以毫无问题地销毁该对象。
引用计数块包含强弱计数,以及用于销毁对象的函数对象。
缺点:它不能很好地适用于多态。
想象一下我们有:
struct A {int x;};
struct B {int y;};
struct C:B,A {int z;};
然后我们以这种方式分配C
。
C* c = new C{};
并将其存储在A*
:
A* a = c;
然后我们将它传递给智能指针到A。它期望控制块在地址a
指向的位置之前,但由于B
存在于A
的继承图中的C
之前,因此存在{ {1}}而是。
这似乎不太理想。
所以我们作弊。我们再次替换B
。但它改为使用注册表在某处注册指针值和大小。我们存储弱/强指针计数(等)。
我们依赖于线性地址空间和类布局。当我们有一个指针new
时,我们只需查找它所在的地址范围。然后我们知道强弱计数。
这个通常具有可怕的性能,特别是多线程,并且依赖于未定义的行为(指针比较指针不指向同一个对象,或者在这种情况下为p
顺序)。
答案 2 :(得分:0)
理论上,可以实现shared_ptr
和weak_ptr
的侵入版本,但由于C ++语言的限制,它可能不安全。
两个引用计数器(强和弱)存储在托管对象的基类RefCounters
中。任何智能指针(共享或弱)都包含指向托管对象的单个指针。共享指针拥有对象本身,共享+弱指针一起拥有对象的内存块。因此,当最后一个共享指针消失时,对象被销毁,但只要存在至少一个指向它的弱指针,它的内存块就会保持活动状态。假设所有涉及的类型仍然从RefCounted
类继承,则转换指针按预期工作。
不幸的是,在C ++中,通常禁止在对象被销毁之后使用对象的成员,尽管大多数实现应该允许这样做而没有问题。有关该方法易读性的更多详细信息,请参阅this question。
以下是智能指针工作所需的基类:
struct RefCounters {
size_t strong_cnt;
size_t weak_cnt;
};
struct RefCounted : public RefCounters {
virtual ~RefCounted() {}
};
这是共享指针定义的一部分(显示如何销毁对象并释放内存块):
template<class T> class SharedPtr {
static_assert(std::is_base_of<RefCounted, T>::value);
T *ptr;
RefCounters *Counter() const {
RefCounters *base = ptr;
return base;
}
void DestroyObject() {
ptr->~T();
}
void DeallocateMemory() {
RefCounted *base = ptr;
operator delete(base);
}
public:
~SharedPtr() {
if (ptr) {
if (--Counter()->strong_cnt == 0) {
DestroyObject();
if (Counter()->weak_cnt == 0)
DeallocateMemory();
}
}
}
...
};
提供样本的完整代码here。