序言
在C 11中有 std::shared_ptr
std::weak_ptr
组合 . 虽然非常有用,但它有一个令人讨厌的问题:你cannot easily construct shared_ptr from a raw pointer . 由于这个缺陷,这些智能指针通常会变成"viral":人们开始完全避免原始指针和引用,并在代码中使用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
的好处?
特别是:
-
shared_ptr模型对对象共享所有权,即当指向它的最后一个shared_ptr被销毁时,该对象被完全销毁 .
-
weak_ptr不会对对象建模所有权,它可以用于解决循环依赖问题 .
可以检查 -
weak_ptr是否有效:当存在指向该对象的shared_ptr时,它是有效的 .
-
shared_ptr可以从有效的weak_ptr构造 .
-
weak_ptr可以从对象的有效原始指针构造 . 如果至少存在一个仍指向该对象的weak_ptr,则原始指针有效 . 从无效指针构造weak_ptr会导致未定义的行为 .
-
整个智能指针系统应该是友好的,就像上面提到的现有系统一样 .
侵入是可以的,即要求用户从给定的基类继承一次 . 当对象已经被破坏时保持对象的内存也是可以的 . 线程安全是非常好的(除非效率太低),但没有它的解决方案也很有趣 . 可以为每个对象分配几个内存块,尽管每个对象有一个内存块是首选 .
3 回答
点1-4和6已经由shared_ptr / weak_ptr建模 .
第5点毫无意义 . 如果共享生命周期,那么如果
weak_ptr
存在但shared_ptr
不存在则没有有效对象 . 任何原始指针都是无效指针 . 对象的生命周期已经结束 . 对象已不复存在 .weak_ptr
不会使对象保持活动状态,它会使控制块保持活动状态 .shared_ptr
使控制块和受控对象保持活动状态 .如果你没有't want to 1250025 memory by combining the control block with the controlled object, don'拨打
make_shared
.如果您不希望
shared_ptr<X>
以病毒式传递给函数,请不要传递它 . 将引用或const引用传递给X
. 如果您打算在函数中管理生命周期,则只需在参数列表中提及shared_ptr
. 如果您只想对shared_ptr指向的内容执行操作,请传递*p
或*p.get()
并接受[const]引用 .覆盖对象上的
new
以在对象实例之前分配控制块 .这是伪侵入的 . 由于已知的偏移,可以从原始指针转换为 . 可以毫无问题地销毁对象 .
引用计数块包含强弱计数,以及用于销毁对象的函数对象 .
缺点:它不能很好地工作在多态 .
想象一下,我们有:
然后我们用这种方式分配
C
.并将其存储在_1250037中:
然后我们将它传递给智能指针到A . 它期望控制块在地址
a
指向之前,但因为B
在继承A
之前存在C
的图形,有一个B
的实例 .这似乎不太理想 .
所以我们作弊 . 我们再次取代
new
. 但它改为使用注册表在某处注册指针值和大小 . 我们存储弱/强指针计数(等) .我们依赖于线性地址空间和类布局 . 当我们有一个指针
p
时,我们只需查找它所在的地址范围 . 然后我们知道强/弱计数 .这个通常具有可怕的性能,尤其是多线程,并且依赖于未定义的行为(指针比较指针不指向同一个对象,或者在这种情况下为
less
顺序) .从理论上讲,可以实现
shared_ptr
和weak_ptr
的侵入版本,但由于C语言的限制,它可能不安全 .两个引用计数器(强和弱)存储在托管对象的基类
RefCounters
中 . 任何智能指针(共享或弱)都包含指向托管对象的单个指针 . 共享指针拥有对象本身,共享弱指针一起拥有对象的内存块 . 因此,当最后一个共享指针消失时,对象被销毁,但只要存在至少一个指向它的弱指针,它的内存块就会保持活动状态 . 鉴于所有涉及的类型仍然从RefCounted
类继承,所以转换指针按预期工作 .不幸的是,在C中,在对象被销毁之后通常禁止与对象成员一起工作,尽管大多数实现应该允许这样做而没有问题 . 有关该方法易读性的更多详细信息,请参见this question .
以下是智能指针工作所需的基类:
这是共享指针定义的一部分(显示如何销毁对象并释放内存块):
带有样本的完整代码可用here .