This page is a snapshot from the LWG issues list, see the Library Active Issues List for more information and the meaning of Open status.
unique_ptr<T>::get_deleter()(p)
to be able to destroy the unique_ptr
Section: 20.3.1.3 [unique.ptr.single] Status: Open Submitter: Rob Desbois Opened: 2013-05-15 Last modified: 2017-03-21
Priority: 3
View other active issues in [unique.ptr.single].
View all other issues in [unique.ptr.single].
View all issues with Open status.
Discussion:
N3337 20.3.1.3.6 [unique.ptr.single.modifiers] contains 2 non-normative notes stating:
[para 4]: "The order of these operations is significant because the call to
get_deleter()
may destroy*this
."[para 5]: "The postcondition does not hold if the call to
get_deleter()
destroys*this
sincethis->get()
is no longer a valid expression."
It seems this wording was created to resolve 998(i) due to the possibility that a unique_ptr
may be
destroyed through deletion of its stored pointer where that directly or indirectly refers to the same unique_ptr
.
If unique_ptr
is required to support circular references then it seems this must be normative text: an implementation
is currently allowed to operate on *this
after the assignment and deletion specified in para 4, since this is only
'disallowed' by the non-normative note.
I propose the following draft rewording:
[para 4]: Effects: assigns p
to the stored pointer, and then if the old value of the stored pointer, old_p
, was not
equal to nullptr
, calls get_deleter()(old_p)
. No operation shall be performed after the call to
get_deleter()(old_p)
that requires *this
to be valid, because the deletion may destroy *this
if it is
referred to directly or indirectly by the stored pointer. [Note: The order of these operations is significant
because the call to
get_deleter()
may destroy *this
. — end note]
get_deleter()(old_p)
destroyed *this
, none. Otherwise,
get() == p
. get_deleter()
destroys *this
since this->get()
is no longer a valid expression. — end note]I expect it will also be necessary to amend the requirements for a deleter, so in addition:
20.3.1.3 [unique.ptr.single] [para 1]: The default type for the template parameter D
is default_delete
.
A client-supplied template argument D
shall be a function object type (20.10), lvalue-reference to function, or
lvalue-reference to function object type for which, given a value d
of type D
and a value ptr
of type
unique_ptr<T, D>::pointer
, the expression d(ptr)
is valid and has the effect of disposing of the pointer
as appropriate for that deleter. Where D
is not an lvalue reference type, d(ptr)
shall be valid if ptr
refers directly or indirectly to the invoking unique_ptr
object.
[2013-10-05, Stephan T. Lavavej comments and provides alternative wording]
In Chicago, we determined that the original proposed change to 20.3.1.3 [unique.ptr.single]/1 was insufficient, because
d
might be a reference to a deleter functor that's destroyed during self-destruction.
X
from doing various things, through the principle of "nothing allows X
to fail in that situation".
For example, v.push_back(v[0])
is required to work for non-empty vectors because nothing allows that to fail. In this case,
the intent to allow self-destruction is already clear.
Additionally, we did not believe that 20.3.1.3.6 [unique.ptr.single.modifiers]/5 had to be changed. The current note is slightly
squirrely but it does not lead to confusion for implementers or users.
Previous resolution from Rob Desbois:
Edit 20.3.1.3 [unique.ptr.single] p1 as indicated:
The default type for the template parameter
D
isdefault_delete
. A client-supplied template argumentD
shall be a function object type (20.10), lvalue-reference to function, or lvalue-reference to function object type for which, given a valued
of typeD
and a valueptr
of typeunique_ptr<T, D>::pointer
, the expressiond(ptr)
is valid and has the effect of disposing of the pointer as appropriate for that deleter. WhereD
is not an lvalue reference type,d(ptr)
shall be valid ifptr
refers directly or indirectly to the invokingunique_ptr
object.Edit 20.3.1.3.6 [unique.ptr.single.modifiers] p4+5 as indicated:
void reset(pointer p = pointer()) noexcept;-3- Requires: The expression
-4- Effects: assignsget_deleter()(get())
shall be well formed, shall have well-defined behavior, and shall not throw exceptions.p
to the stored pointer, and then if the old value of the stored pointer,old_p
, was not equal tonullptr
, callsget_deleter()(old_p)
. No operation shall be performed after the call toget_deleter()(old_p)
that requires*this
to be valid, because the deletion may destroy*this
if it is referred to directly or indirectly by the stored pointer.[Note: The order of these operations is significant because the call to-5- Postconditions: If the callget_deleter()
may destroy*this
. — end note]get_deleter()(old_p)
destroyed*this
, none. Otherwise,get() == p
.[Note: The postcondition does not hold if the call toget_deleter()
destroys*this
sincethis->get()
is no longer a valid expression. — end note]
Previous resolution [SUPERSEDED]:
This wording is relative to N3691.
Edit 20.3.1.3 [unique.ptr.single] p1 as indicated:
The default type for the template parameter
D
isdefault_delete
. A client-supplied template argumentD
shall be a function object type (20.10), lvalue-reference to function, or lvalue-reference to function object type for which, given a valued
of typeD
and a valueptr
of typeunique_ptr<T, D>::pointer
, the expressiond(ptr)
is valid and has the effect of disposing of the pointer as appropriate for that deleter.d(ptr)
shall be valid even if it triggers the destruction ofd
or (ifD
is an lvalue reference to function object type) the function object thatd
refers to.
[2015-05, Lenexa]
After some discussion in Lenexa there was some wavering on if the added sentence is necessary. Here is example code that
demonstrates why the extra sentence is necessary. In this example the call to d(ptr)
is valid, however the deleter
references *this
after destructing its element:
#include <cassert> #include <memory> #include <iostream> class Deleter { int state_ = 0; enum { destructed = -4, self_move_assigned = -3, move_assigned_from = -2, move_constructed_from = -1 }; public: ~Deleter() {state_ = destructed;} Deleter() = default; Deleter(Deleter const&) = default; Deleter& operator=(Deleter const&) = default; Deleter(Deleter&& a) noexcept : state_(a.state_) {a.state_ = move_constructed_from;} Deleter& operator=(Deleter&& a) noexcept { if (this == &a) state_ = self_move_assigned; else { state_ = a.state_; a.state_ = move_assigned_from; } return *this; } Deleter(int state) : state_(state) { assert(state >= 0); } template <class T> void operator()(T* t) const { std::cout << "Deleter beginning operator()(T*)\n"; std::cout << "The deleter = " << *this << '\n'; std::cout << "Deleter about to destruct the X.\n"; delete t; std::cout << "Deleter has destructed the X.\n"; std::cout << "The deleter = " << *this << '\n'; std::cout << "Deleter ending operator()(T*)\n"; } friend std::ostream& operator<<(std::ostream& os, const Deleter& a) { switch (a.state_) { case destructed: os << "**destructed**"; break; case self_move_assigned: os << "self_move_assigned"; break; case move_assigned_from: os << "move_assigned_from"; break; case move_constructed_from: os << "move_constructed_from"; break; default: os << a.state_; break; } return os; } }; struct X { Deleter deleter_{1}; }; int main() { auto xp = new X; { std::unique_ptr<X, Deleter&> p(xp, xp->deleter_); std::cout << "unique_ptr is constructed.\n"; std::cout << "The deleter = " << p.get_deleter() << '\n'; std::cout << "Destructing unique_ptr...\n"; } std::cout << "unique_ptr is destructed.\n"; }
Which outputs:
unique_ptr is constructed. The deleter = 1 Destructing unique_ptr... Deleter beginning operator()(T*) The deleter = 1 Deleter about to destruct the X. Deleter has destructed the X. The deleter = **destructed** Deleter ending operator()(T*) unique_ptr is destructed.
The line "The deleter = **destructed**
" represents the deleter referencing itself after it has been destructed by the
d(ptr)
expression, but prior to that call returning.
The expression
d(ptr)
shall not refer to the objectd
after it executesptr->~T()
.
[2015-07, Telecon]
Geoffrey: Deleter may or may not execute ~T().
Alisdair: After the destructor after the element has run. Say it in words instead of code.
Howard will provide updated wording. Perhaps need both normative and non-normative wording.
[2015-08-03, Howard updates P/R per telecon discussion.]
[2017-03-04, Kona]
This is related to 2751(i), which has been suggested NAD.
STL wants "Effects equivalent to" here - say it in code. Marshall to research.
Proposed resolution:
This wording is relative to N4431.
Edit 20.3.1.3 [unique.ptr.single] p1 as indicated:
The default type for the template parameter
D
isdefault_delete
. A client-supplied template argumentD
shall be a function object type (20.9), lvalue-reference to function, or lvalue-reference to function object type for which, given a valued
of typeD
and a valueptr
of typeunique_ptr<T, D>::pointer
, the expressiond(ptr)
is valid and has the effect of disposing of the pointer as appropriate for that deleter. The expressiond(ptr)
, if it destructs the object referred to byptr
, shall not refer to the objectd
after it destructs*ptr
. [Note: The object being destructed may control the lifetime ofd
. — end note]