It is widespread that you can use shared_ptr to store a pointer to an incomplete type if the pointer can be deleted (with well-defined behavior) during the construction of shared_ptr . For example, the PIMPL method:
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 )
This works as the object "delete object". The "owner object" (*) is created when building shared_ptr in pimpl( new impl ) and is saved after erasing the type inside shared_ptr . This "owner object" is later used to destroy the object that it points to. Therefore, it is safe to provide a built-in interface .
Question: Where does the standard guarantee its safety?
(*) Does not apply to the Standard, see below, but it either calls a custom divider or calls an expression-expression. This object is usually stored as part of an accounting object, applying erasure of styles and calling user delete / delete-expression in a virtual function. At this point, the delete statement should also be well-formed.
Referring to the latest project in the github repository (94c8fc71, revising N3797), [util.smartptr.shared.const]
template<class Y> explicit shared_ptr(Y* p);
3 Required: p converted to T* . Y must be a full type. The delete p expression must be well-formed, must have well-defined behavior, and should not rule out an exception.
4 Effects: Creates a shared_ptr object to which the pointer p belongs.
5 Postconditions: use_count() == 1 && get() == p .
6 Throws: bad_alloc or an exception defined by the implementation when a resource other than memory cannot be received.
Note. To do this, ctor shared_ptr does not need to own a deleter. By default, the standard means that a user debiter, for example, you provide during construction as an additional parameter (or shared_ptr acquires / shares one of the other shared_ptr , for example, by assigning a copy). Also see (See [Util.smartptr.shared.const] / 9). Implementations (boost, libstdC ++, MSVC, and I assume that every reasonable implementation) always store the "owner object".
Since the deletir is a user deleter, the shared_ptr destructor is defined in terms of delete (delete-expression) if there is no user deletion:
[util.smartptr.shared.dest]
~shared_ptr();
1 Effects:
- If
*this empty or has ownership of another instance of shared_ptr ( use_count() > 1 ), there are no side effects. - Otherwise, if
*this belongs to p and deleter d , d(p) is called. - Otherwise,
*this belongs to the pointer p , and delete p - is called.
I assume that it is assumed that correctly deleting the stored pointer requires an implementation, even if in the shared_ptr dtor area the expression-expression is poorly formed or will call UB. (The expression expression must be well-formed and have the correct behavior in ctor.) So, the question is:
Question: Where is this required?
(Or am I just too picky, and it is obvious that implementations should use an "owner object"?)