Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Does shared_ptr still own its object when calling the deleter?

I have a std::shared_ptr with a custom deleter and in that deleter, I would like to take a temporary copy of the original std::shared_ptr. Expressed in code form:

struct Foo : public std::enable_shared_from_this<Foo>
{};

void deleter(Foo *f)
{
  {
    std::shared_ptr<Foo> tmp = f->shared_from_this(); // Line A
  }
  delete f;
}

int main()
{
  std::shared_ptr<Foo> foo(new Foo, &deleter);
}

My question is: on line A, can anything be said about the call of shared_from_this()? Is it legal? If so, does the standard say anything about its return value? If we replace enable_shared_from_this with a different weak_ptr or global reference to foo, will the answer be the same?

Clang with libc++ and gcc with libstdc++ both produce code which terminates on a bad_weak_ptr exception, but I can't seem to trace this as required by the standard. Is this implementation-specific, or am I missing a rule?

All the relevant rules I found (quoting C++11):

20.7.2.2.2 shared_ptr destructor

1 ... if *this owns an object p and a deleter d, d(p) is called
2 [Note: ... Since the destruction of *this decreases the number of instances that share ownership with *this by one, after *this has been destroyed all shared_ptr instances that shared ownership with *this will report a use_count() that is one less than its previous value. —end note]

20.7.2.2.5 shared_ptr observers

7 use_count Returns: the number of shared_ptr objects, *this included, that share ownership with *this, or 0 when *this is empty.

To me, it seems it's not clear whether the decrement of use_count happens before or after the call of the deleter. Is getting bad_weak_ptr a reliable result, or is this simply unspecified?

Note that I am intentionally avoiding a situation where a pointer like tmp in my example code would outlive the deleter execution.

like image 932
Angew is no longer proud of SO Avatar asked Oct 09 '17 12:10

Angew is no longer proud of SO


1 Answers

consider

[c++14-12.4-15]Once a destructor is invoked for an object, the object no longer exists;

and

[c++14-20.8.2.4-7]shared_from_this()[...]Requires: enable_shared_from_this shall be an accessible base class of T. *this shall be a subobject of an object t of type T. There shall be at least one shared_ptr instance p that owns &t.

so, considering that the deleter is invoked by the shared_ptr destructor, calling shared_from_this() in the deleter of the last shared_ptr owning it results in undefined behaviour.

EDIT: as pointed out by YSC, in C++17 shared_from_this() is required to behave as the corresponding weak_ptr conversion call. This complicates matters though, because it's not clear what weak_ptr::expired() should return at deleter call ... anyway, taking the quoted 20.7.2.2.2 note literally, a bad_weak_ptr should be raised in this case.

like image 104
Massimiliano Janes Avatar answered Oct 16 '22 16:10

Massimiliano Janes