[英]Is a shared_ptr's deleter stored in memory allocated by the custom allocator?
假设我有一个带有自定义分配器和自定义删除器的shared_ptr
。
我在标准中找不到任何关于删除器应该存储在哪里的内容:它没有说自定义分配器将用于删除器的 memory,也没有说不会。
这是未指定还是我只是错过了什么?
C++ 11 中的 util.smartptr.shared.const/9:
效果:构造一个 shared_ptr object 拥有 object p 和删除器 d。 第二个和第四个构造函数要使用 a 的副本来分配 memory 供内部使用。
第二个和第四个构造函数有这些原型:
template<class Y, class D, class A> shared_ptr(Y* p, D d, A a);
template<class D, class A> shared_ptr(nullptr_t p, D d, A a);
在最新的草案中, util.smartptr.shared.const/10 等效于我们的目的:
效果:构造一个 shared_ptr object 拥有 object p 和删除器 d。 当 T 不是数组类型时,第一个和第二个构造函数启用 shared_from_this 和 p。 第二个和第四个构造函数要使用 a 的副本来分配 memory 供内部使用。 如果抛出异常,则调用 d(p)。
因此,如果需要在已分配的 memory 中分配它,则使用分配器。 根据现行标准和相关缺陷报告,分配不是强制性的,而是由委员会承担。
虽然shared_ptr
的接口允许实现永远没有控制块并且所有shared_ptr
和weak_ptr
都放在一个链表中,但实际上并没有这样的实现。 此外,已经修改了措辞,例如假设use_count
是共享的。
删除器只需要移动可构造的。 因此,在shared_ptr
中不可能有多个副本。
可以想象一种实现,它将删除器放在专门设计的shared_ptr
中,并在删除特殊的shared_ptr
时移动它。 虽然实现看起来是一致的,但它也很奇怪,特别是因为使用计数可能需要一个控制块(用使用计数做同样的事情也许是可能的,但更奇怪)。
我发现的相关 DR: 545 、 575 、 2434 (它们承认所有实现都使用控制块,并且似乎暗示多线程约束在某种程度上要求它), 2802 (它要求删除器只能移动可构造,因此阻止实现删除器在几个shared_ptr
之间复制)。
从std::shared_ptr我们有:
控制块是一个动态分配的 object,它包含:
从std::allocate_shared我们得到:
template< class T, class Alloc, class... Args >
shared_ptr<T> allocate_shared( const Alloc& alloc, Args&&... args );
构造一个 T 类型的 object 并将其包装在 std::shared_ptr [...] 中,以便为共享指针的控制块和 T object 使用一个分配。
所以看起来std::allocate_shared应该用你的Alloc
分配deleter
。
编辑:从n4810
§20.11.3.6 Creation [util.smartptr.shared.create]
1 除非另有说明,否则适用于所有
make_shared
、allocate_shared
、make_shared_default_init
和allocate_shared_default_init
重载的通用要求如下所述。[...]
7 备注: (7.1) —实现应该执行不超过一个 memory 分配。 [注意:这提供了等效于侵入式智能指针的效率。 ——尾注]
[强调所有我的]
所以标准是说std::allocate_shared
应该使用Alloc
作为控制块。
我相信这是未指定的。
以下是相关构造函数的规范: [util.smartptr.shared.const]/10
template<class Y, class D> shared_ptr(Y* p, D d); template<class Y, class D, class A> shared_ptr(Y* p, D d, A a); template <class D> shared_ptr(nullptr_t p, D d); template <class D, class A> shared_ptr(nullptr_t p, D d, A a);
效果:构造一个
shared_ptr
object 拥有 objectp
和删除器d
。 当T
不是数组类型时,第一个和第二个构造函数启用shared_from_this
和p
。 第二个和第四个构造函数要使用a
的副本来分配 memory 供内部使用。 如果抛出异常,则调用d(p)
。
现在,我的解释是,当实现需要 memory 供内部使用时,它通过a
. 这并不意味着实现必须使用这个 memory 来放置所有东西。 例如,假设有这个奇怪的实现:
template <typename T>
class shared_ptr : /* ... */ {
// ...
std::aligned_storage<16> _Small_deleter;
// ...
public:
// ...
template <class _D, class _A>
shared_ptr(nullptr_t, _D __d, _A __a) // for example
: _Allocator_base{__a}
{
if constexpr (sizeof(_D) <= 16)
_Construct_at(&_Small_deleter, std::move(__d));
else
// use 'a' to allocate storage for the deleter
}
// ...
};
此实现是否“使用a
的副本分配 memory 供内部使用”? 是的,它确实。 除了a
. 这种幼稚的实现存在许多问题,但假设它切换到使用分配器,但最简单的情况除外,在最简单的情况下, shared_ptr
是直接从指针构造的,从不复制、移动或以其他方式引用,并且没有其他复杂情况。 关键是,仅仅因为我们无法想象一个有效的实现本身并不能证明它在理论上不存在。 我并不是说这样的实现实际上可以在现实世界中找到,只是标准似乎并没有积极禁止它。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.