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

时间:2019-11-19 11:25:49

标签: c++ language-lawyer c++17 shared-ptr allocator

说我有一个shared_ptr,它带有一个自定义分配器一个自定义删除器。

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

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

3 个答案:

答案 0 :(得分: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等效于我们的目的:

  

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

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

  • 尽管shared_ptr的接口允许一种实现,其中永远没有控制块,并且所有shared_ptrweak_ptr都放在一个链表中,但是没有这样的实现在实践中。另外,例如,假设use_count是共享的,措词也已修改。

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

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

我发现了相关的DR:5455752434(它们承认所有实现都在使用控制块,并且似乎暗示多线程约束在某种程度上要求控制块), 2802(这要求删除程序仅可移动地构造,因此阻止了在多个shared_ptr之间复制删除程序的实现。)

答案 1 :(得分: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应该为您的deleter分配Alloc

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

  

1适用于所有make_shared allocate_shared make_shared_default_init,   除非另有说明,否则allocate_shared_default_init重载如下所述。

     

[...]

     

7条评论:   (7.1)— 实现只能执行一次内存分配。 [注意:这提供了   效率相当于侵入式智能指针。 —尾注]

[强调我的全部]

因此,标准要求std::allocate_shared 应该Alloc用于控制块。

答案 2 :(得分: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的{​​{1}}对象。如果d不是数组类型,则第一个和   第二个构造函数使用T启用shared_­from_­this。第二   第四个构造函数应使用p的副本来分配内存   供内部使用。如果引发异常,则会调用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是直接从指针构造的,并且永远不会被复制,移动或引用,并且存在没有其他并发症。关键是,仅仅因为我们无法想象一个有效的实现本身并不能证明它在理论上是不存在的。我并不是说这样的实现实际上可以在现实世界中找到,只是该标准似乎并未积极禁止它。