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

时间:2013-11-07 20:51:33

标签: c++ c++11 shared-ptr language-lawyer

It's widely known您可以使用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
};

[main.cpp中]

int main()
{
    interface i;
    i.foo();
}

[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();
}

这可以作为“删除对象”“所有者对象”(*)在构造shared_ptr pimpl( new impl )期间创建,并在类型擦除后存储shared_ptr。此“所有者对象”稍后用于销毁指向的对象。这就是为什么提供interface内联析构函数应该是安全的。

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

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


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

template<class Y> explicit shared_ptr(Y* p);
     

3要求:p必须   可转换为T*Y应为完整类型。表达式delete p   应形成良好,行为明确,不得行为   抛出异常。

     

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

     

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

     

6抛出:bad_alloc,或者a时实现定义的异常   无法获得除内存以外的资源。

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

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

[util.smartptr.shared.dest]

~shared_ptr();
     

1效果:

     
      
  • 如果*this 为空或与之共享所有权   另一个shared_ptr实例(use_count() > 1),没有任何一方   的效果。
  •   
  • 否则,如果*this 拥有对象p和删除者dd(p)   被称为。
  •   
  • 否则,*this 拥有指针p,而delete p是   调用。
  •   

我假设 intent 是一个实现需要正确删除存储的指针,即使在shared_ptr dtor的范围内,delete-expression是不正确的或者会调用UB。 (delete-expression必须格式良好,并且在ctor中有明确定义的行为。)所以,问题是

问题:这需要在哪里?

(或者我太挑剔了,显然某些实现需要使用“所有者对象”?)

1 个答案:

答案 0 :(得分: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); }
  // ...
};

现在shared_ptr有一个SpOwner*,当计数降到零时,它会调用虚函数dispose(),该函数调用delete或调用删除器,具体取决于方式对象是构造的。是否构造SpOwnerSpOwnerWithDeleter的决定是在构造shared_ptr时做出的,并且当shared_ptr被销毁时该类型仍然相同,所以如果它需要处理拥有的指针然后才会做正确的事情。