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(); }
这可以作为“删除对象”“所有者对象”(*)在pimpl(new impl)中构建shared_ptr期间创建,并在shared_ptr中的类型擦除之后存储.此“所有者对象”稍后用于销毁指向的对象.这就是为什么提供接口的内联析构函数应该是安全的.
问题:标准在哪里保证它是安全的?
(*)就标准而言,不是删除器,见下文,但它可以调用自定义删除器或调用delete-expression.该对象通常作为簿记对象的一部分存储,应用类型擦除并在虚函数中调用自定义删除/删除表达式.此时,delete-expression也应该是格式良好的.
参考github存储库中的最新草案(94c8fc71,修改N3797),[util.smartptr.shared.const]
06003
3 Requires:
p
shall be
convertible toT*
.Y
shall be a complete type. The expressiondelete p
shall be well formed,shall have well defined behavior,and shall not
throw exceptions.4 Effects: Constructs a
shared_ptr
object that owns
the pointerp
.5 Postconditions:
use_count() == 1 && get() == p
.6 Throws:
bad_alloc
,or an implementation-defined exception when a
resource other than memory could not be obtained.
注意:对于此ctor,shared_ptr不需要拥有删除器.通过删除,标准似乎意味着自定义删除,例如您在构造期间提供的附加参数(或shared_ptr从另一个shared_ptr获取/共享一个,例如通过复制分配).另见(另见[util.smartptr.shared.const] / 9).实现(boost,libstdc,MSVC,我猜每个理智的实现)总是存储“所有者对象”.
由于删除器是自定义删除器,如果没有自定义删除器,则shared_ptr的析构函数是根据delete(delete-expression)定义的:
[util.smartptr.shared.dest]
06004
1 Effects:
- If
*this
is empty or shares ownership with
anothershared_ptr
instance (use_count() > 1
),there are no side
effects.- Otherwise,if
*this
owns an objectp
and a deleterd
,d(p)
is called.- Otherwise,
*this
owns a pointerp
,anddelete p
is
called.
我假设的意图是,即使在shared_ptr dtor的范围内,delete-expression的格式错误或者会调用UB,也需要实现正确删除存储的指针. (delete-expression必须格式正确并且在ctor中具有明确定义的行为.)所以,问题是
问题:这需要在哪里?
(或者我太挑剔了,而且很明显,某些实现需要使用“所有者对象”?)
解决方法
Question: Where is this required?
如果不需要,析构函数将具有未定义的行为,并且标准不习惯于要求未定义的行为:-)
如果满足构造函数的前提条件,则析构函数不会调用未定义的行为.实现如何确保未指定,但您可以假设它正确,并且您不需要知道如何.如果不期望实现做正确的事,则析构函数将具有前提条件.
(Or am I just too nit-picky and it’s obvIoUs somehow that the implementations are required to use a “owner object”?)
是的,必须创建一些额外的对象来拥有指针,因为引用计数(或其他簿记数据)必须在堆上而不是任何特定的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或调用删除器,具体取决于对象的构造方式.是否构造SpOwner或SpOwnerWithDeleter是在构造shared_ptr时做出的,并且当shared_ptr被销毁时该类型仍然相同,因此如果它需要处理拥有的指针,那么它将执行正确的操作.