shared_ptr的dtor是否需要使用"删除者"?

dyp*_*dyp 7 c++ shared-ptr language-lawyer c++11

广泛 知道,你可以使用shared_ptr一个存储指向不完全类型,只要指针可以(有良好定义的行为)的施工过程中被删除shared_ptr.例如,PIMPL技术:

struct interface
{
    interface();                 // out-of-line definition required
    ~interface() = default;   // public inline member, even if implicitly defined
    void foo();
private:
    struct impl;                 // incomplete type
    std::shared_ptr<impl> pimpl; // pointer to incomplete type
};
Run Code Online (Sandbox Code Playgroud)

[main.cpp中]

int main()
{
    interface i;
    i.foo();
}
Run Code Online (Sandbox Code Playgroud)

[interface.cpp]

struct interface::impl
{
    void foo()
    {
        std::cout << "woof!\n";
    }
};

interface::interface()
    : pimpl( new impl ) // `delete impl` is well-formed at this point
{}

void interface::foo()
{
    pimpl->foo();
}
Run Code Online (Sandbox Code Playgroud)

这个作品作为一个"删除器对象" "所有者对象"(*)所述的构建过程中被创建shared_ptrpimpl( new impl ),和内部的类型擦除后存储shared_ptr.此"所有者对象"稍后用于销毁指向的对象.这就是为什么提供内联析构函数应该是安全的原因interface.

问题:标准在哪里保证它是安全的?

(*)在标准方面不是删除器,见下文,但它可以调用自定义删除器或调用delete-expression.该对象通常作为簿记对象的一部分存储,应用类型擦除并在虚函数中调用自定义删除/删除表达式.此时,delete-expression也应该是格式良好的.


参考github存储库中的最新草案(94c8fc71,修改N3797),[util.smartptr.shared.const]

template<class Y> explicit shared_ptr(Y* p);
Run Code Online (Sandbox Code Playgroud)

3要求:p应可转换为T*.Y应该是一个完整的类型.表达式delete p 应格式良好,具有明确定义的行为,不得抛出异常.

4效果:构造shared_ptr拥有指针的对象p.

5后置条件:use_count() == 1 && get() == p.

6抛出:bad_alloc或无法获取内存以外的资源时的实现定义的异常.

注意:对于此ctor,shared_ptr 不需要拥有删除器.通过删除,标准似乎意味着自定义删除,例如您在构造期间提供的附加参数(或shared_ptr从另一个参数获取/共享shared_ptr,例如通过复制分配).另见(另见[util.smartptr.shared.const]/9).实现(boost,libstdc ++,MSVC,我猜每个理智的实现)总是存储"所有者对象".

由于删除器自定义删除器,因此如果没有自定义删除,则析构函数shared_ptr是根据delete(delete-expression)定义的:

[util.smartptr.shared.dest]

~shared_ptr();
Run Code Online (Sandbox Code Playgroud)

1效果:

  • 如果*this或与另一个shared_ptr实例(use_count() > 1)共享所有权,则没有副作用.
  • 否则,如果*this 拥有一个对象p和一个删除器d,d(p) 则被调用.
  • 否则,*this 拥有一个指针p,并被delete p调用.

我假设意图是需要一个实现来正确删除存储的指针,即使在shared_ptrdtor 的范围内,delete-expression是格式错误的或者会调用UB.(delete-expression必须格式正确,并且在ctor中有明确定义的行为.)所以,问题是

问题:这需要在哪里?

(或者我太挑剔了,而且很明显,实现是否需要使用"所有者对象"?)

Jon*_*ely 4

问: 哪里需要这个?

如果不需要,析构函数将具有未定义的行为,并且标准不习惯要求未定义的行为:-)

如果满足构造函数的先决条件,则析构函数将不会调用未定义的行为。实现如何确保这一点尚未指定,但您可以假设它是正确的,并且您不需要知道如何实现。如果预计实现不会做正确的事,那么析构函数就会有一个先决条件。

(或者我是不是太挑剔了,很明显,实现需要使用“所有者对象”?)

是的,必须创建一些额外的对象来拥有指针,因为引用计数(或其他簿记数据)必须位于堆上,而不是任何特定shared_ptr实例的一部分,因为它可能需要比任何特定实例更长寿。所以是的,有一个额外的对象,它拥有指针,您可以将其称为所有者对象。如果用户没有提供删除器,则该所有者对象仅调用delete. 例如:

template<typename T>
struct SpOwner {
  long count;
  long weak_count;
  T* ptr;
  virtual void dispose() { delete ptr; }
  // ...
};

template<typename T, typename Del>
struct SpOwnerWithDeleter : SpOwner<T> {
  Del del;
  virtual void dispose() { del(this->ptr); }
  // ...
};
Run Code Online (Sandbox Code Playgroud)

现在 ashared_ptr有 a SpOwner*,当计数降到零时,它会调用虚拟函数,dispose()该函数会调用delete或调用删除器,具体取决于对象的构造方式。是否构造 anSpOwner或 an的决定SpOwnerWithDeleter是在构造时做出的shared_ptr,并且在销毁时该类型仍然相同shared_ptr,因此如果它需要处置所拥有的指针,那么它将做正确的事情。