7

众所周知 您可以使用 a来存储指向不完整类型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
};

[主.cpp]

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

[接口.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 “所有者对象”(*)在in的构造过程中创建pimpl( new impl ),并在类型擦除后存储在shared_ptr. 这个“所有者对象”稍后用于销毁指向的对象。这就是为什么提供interface.

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

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


参考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,或当无法获取内存以外的资源时发生实现定义的异常。

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

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

[util.smartptr.shared.dest]

~shared_ptr();

1 效果:

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

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

问题:这在哪里需要?

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

4

1 回答 1

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); }
  // ...
};

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

于 2013-11-07T21:06:00.310 回答