是否将shared_ptr的删除器存储在自定义分配器分配的内存中?


22

说我有shared_ptr一个自定义分配器自定义删除器的。

我在标准中找不到任何内容讨论删除器的存储位置:它没有说自定义分配器将用于删除器的内存,也没有说 不会

这是未指定的还是我只是缺少了什么?

Answers:


11

C ++ 11中的util.smartptr.shared.const / 9:

效果:构造一个拥有对象p和删除器d的shared_ptr对象。第二和第四构造函数应使用a的副本为内部使用分配内存。

第二和第四构造函数具有以下原型:

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等效于我们的目的:

效果:构造一个拥有对象p和删除器d的shared_ptr对象。当T不是数组类型时,第一和第二个构造函数使用p启用shared_from_this。第二和第四构造函数应使用a的副本为内部使用分配内存。如果引发异常,则调用d(p)。

因此,如果需要在分配的内存中分配它,则使用分配器。根据当前标准和相关缺陷报告,分配不是强制性的,而是由委员会承担。

  • 虽然界面shared_ptr允许那里是从来没有一个控制模块和所有的实现shared_ptr,并weak_ptr放入一个链表,这两个东西实际上没有这样的实现。此外,措词已被修改,例如假设use_count共享。

  • 删除程序仅需移动可构造的。因此,不可能有多个副本shared_ptr

可以想像一种将删除程序放入经过特殊设计的实现中,并在删除特殊设置shared_ptr时将其移动的实现shared_ptr。尽管实现看起来是一致的,但也很奇怪,特别是因为使用计数可能需要一个控制块(使用计数来做相同的事情也许是可能的,但甚至很奇怪)。

相关DR的我发现:5455752434(其承认所有实现都使用控制块和似乎意味着多线程约束稍微授权的话),2802(这要求删除器仅移动constructible并因此防止实现,其中删除程序将在多个之间复制shared_ptr)。


2
“为内部使用分配内存”如果实现开始时不打算为内部使用分配内存该怎么办?它可以使用一个成员。
LF

1
@LF不能,该界面不允许这样做。
AProgrammer

从理论上讲,它仍然可以使用某种“小型删除器优化”,对吗?
LF

奇怪的是,我找不到使用同一分配器(的副本a)来释放该内存的任何信息。这意味着将存储该副本a。[util.smartptr.shared.dest]中没有有关此信息。
丹尼尔·兰格

1
@DanielsaysreinstateMonica,我想知道是否在util.smartptr.shared / 1中:“ shared_ptr类模板存储了一个指针,通常是通过new获取的。shared_ptr实现了共享所有权的语义;该指针的最后剩余所有者负责销毁对象,或释放与存储的指针关联的资源。” 在释放与所述存储的指针相关联的资源不是为这一点。但是控制块也应保留到删除最后一个弱指针为止。
AProgrammer

4

std :: shared_ptr我们有:

控制块是一个动态分配的对象,它包含:

  • 指向托管对象的指针或托管对象本身;
  • 删除器(类型删除);
  • 分配器(类型擦除);
  • 拥有托管对象的shared_ptrs的数量;
  • 引用托管对象的weak_ptrs的数量。

std :: allocate_shared我们得到:

template< class T, class Alloc, class... Args >
shared_ptr<T> allocate_shared( const Alloc& alloc, Args&&... args );

构造类型为T的对象,并将其包装在std :: shared_ptr [...]中,以便对共享指针的控制块和T对象使用一种分配

所以看起来std :: allocate_shared应该deleterAlloc

编辑:从n4810§20.11.3.6创建[util.smartptr.shared.create]

1适用于所有的共同要求make_sharedallocate_sharedmake_shared_default_init,和allocate_shared_default_init过载,除非另有说明,在下面描述。

[...]

7备注:(7.1)— 实现应执行不超过一个的内存分配。[注:这提供了等效于侵入式智能指针的效率。—尾注]

[强调所有我的]

因此,标准是说,std::allocate_shared 应该使用Alloc的控制块。


1
抱歉,cppreference不是规范性文本。这是一个很好的资源,但不一定要解决语言律师的问题。
StoryTeller-Unslander Monica,

@ StoryTeller-UnslanderMonica完全同意-查看最新标准,找不到任何使用cppreference的东西。
Paul Evans

@ PaulEvans,eel.is
c

找到n4810并更新答案。
保罗·埃文斯

1
但是,这里所谈论的make_shared不是构造函数本身。不过,我可以将成员用于小型删除器。
LF

3

我相信这是未指定的。

以下是相关构造函数的规范:[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拥有该对象p和Deleter 的对象d。当T不是数组类型,第一和第二构造使shared_­from_­thisp。第二和第四构造函数应使用的副本a为内部使用分配内存。如果引发异常,d(p)则调用。

现在,我的解释是,当实现需要内存供内部使用时,可以使用来实现a。这并不意味着实现必须使用此内存来放置所有内容。例如,假设有一个奇怪的实现:

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为内部使用分配内存”?是的,它确实。除了使用,它从不分配内存a。这个幼稚的实现有很多问题,但可以说,除了最简单的情况外,它都转换为使用分配器shared_ptr。关键是,仅仅因为我们无法想象一个有效的实现本身并不能证明它在理论上是不存在的。我并不是说这样的实现实际上可以在现实世界中找到,只是该标准似乎并未积极禁止它。


IMO your shared_ptrfor small type在堆栈上分配内存。因此不符合标准要求
bartop

1
@bartop它不会“分配”堆栈上的任何内存。_Smaller_deleter无条件是shared_ptr表示的一部分。在此空间上调用构造函数并不意味着分配任何内容。否则,即使持有指向控制块的指针也算作“分配内存”,对吗?:-)
LF

但是删除器不需要是可复制的,那么它将如何工作?
Nicol Bolas

@NicolBolas嗯...使用std::move(__d),并退至需要allocate复制时。
LF
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.