10

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.

curiousguy
  • 8,038
  • 2
  • 40
  • 58
Angew is no longer proud of SO
  • 167,307
  • 17
  • 350
  • 455
  • `I would like to take a temporary copy of the original std::shared_ptr` 0_0, why?! – David Haim Oct 09 '17 at 12:56
  • 3
    If you don't want `tmp` to outlive the scope of the deleter, why not use the raw pointer `Foo *f`? – Daniel Trugman Oct 09 '17 at 12:59
  • @DavidHaim Long story about a complex framework of interface management, ABI compatibility, and casts between virtual base classes, all built on top of `shared_ptr`. – Angew is no longer proud of SO Oct 09 '17 at 13:02
  • @DanielTrugman Same as my previous reply to David Haim: legacy code reasons. – Angew is no longer proud of SO Oct 09 '17 at 13:03
  • @Angew instead, I would add some virtual function to Foo named "move" which moves the content of the pointer. if the deleter decides it's not the time for the object to die, it moves it rather than increase the ref count – David Haim Oct 09 '17 at 13:05
  • @Angew: Maybe you can just make a dummy ``shared_ptr`` with a no-op deleter: ``shared_ptr{f, [](Foo*){}}`` Of course, if a copy of this ``shared_ptr`` persists, you're in deep trouble. – Arne Vogel Oct 09 '17 at 16:00
  • Common sense dictates that the deleter is called because the last owning ptr has released the object. What the text says is a bit unclear, but you should go with common sense. – curiousguy Oct 21 '17 at 05:55

1 Answers1

13

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.

Massimiliano Janes
  • 5,524
  • 1
  • 10
  • 22
  • I don't get how 12.4-15 is relevant here. What destructor are you considering here? – YSC Oct 09 '17 at 13:08
  • Ok then, `main::foo` does not exist once we enter `deleter()`. Why does it matter? – YSC Oct 09 '17 at 13:12
  • @YSC, because if foo does not exist, you are violating a shared_from_this() precondition ... – Massimiliano Janes Oct 09 '17 at 13:15
  • 2
    In deed! I do get it now. From [cppreference.com](http://en.cppreference.com/w/cpp/memory/enable_shared_from_this/shared_from_this): _"It is permitted to call `shared_from_this` only on a previously shared object, i.e. on an object managed by `std::shared_ptr`. Otherwise the behavior is undefined (until C++17) `std::bad_weak_ptr` is thrown (by the `shared_ptr` constructor from a default-constructed `weak_this`) (since C++17). "_ – YSC Oct 09 '17 at 13:17
  • @YSC, thank you for pointing out the c++17 change, adding to answer for completness sake ... – Massimiliano Janes Oct 09 '17 at 13:22
  • On the contrary, an object that exists enough in its destructor to pass the this pointer to other objects and the language goes out of its way to provide sane behavior if you call virtual methods in this state. Now if you try to downcast to a derived class all bets are off. – Joshua Oct 09 '17 at 18:47
  • @Joshua The std says that the lifetime ends when the dtor begins, then tries to make that irrelevant by defining what the ending of the lifetime made undefined – curiousguy Oct 15 '17 at 14:44