7

Given this class which is enable_shared_from_this:

class connection : public std::enable_shared_from_this<connection>
{
   //...
};

Suppose I create two instances of std::shared_ptr from the same connection* as follows:

std::shared_ptr<connection> rc(new connection);

std::shared_ptr<connection> fc(rc.get(), [](connection const * c) {
                                   std::cout << "fake delete" << std::endl;
                               });

So far its good, as the resource { connection* } is owned by a single shared_ptrrc to be precise, and fc just have a fake deleter.

After that, I do this:

auto sc = fc->shared_from_this();
//OR auto sc = rc->shared_from_this(); //does not make any difference!

Now which shared_ptrrc or fc — would sc share its reference-count with? In other words,

std::cout << rc->use_count() << std::endl;
std::cout << fc->use_count() << std::endl;

What should these print? I tested this code and found rc seems to have 2 references while fc just 1.

My question is, why is that? and what should be the correct behavior and its rationale?

I'm using C++11 and GCC 4.7.3.

curiousguy
  • 8,038
  • 2
  • 40
  • 58
Nawaz
  • 353,942
  • 115
  • 666
  • 851
  • `fc` knows nothing about `rc` as it is constructed with a raw pointer as if it were a new object. – jtbandes Nov 13 '17 at 05:49
  • 2
    @jtbandes - In general. When inheriting from `enable_shared_from_this` it can know more. – StoryTeller - Unslander Monica Nov 13 '17 at 05:52
  • 1
    Interesting! Thanks for the link to the spec :) – jtbandes Nov 13 '17 at 05:56
  • A fake owning smart ptr (non owning "owning" smart ptr) is very problematic. I can see how it might be used for object whose lifetime is eternal (not limited to static objects w/o a dtor), but in general it's a violation of contract if keeping a copy of a smart ptr doesn't extend the lifetime of the object "owned". – curiousguy Nov 28 '17 at 22:53
  • @curiousguy: Well, the idea of "fake" resource-owning smart ptr could be good in certain scenario. Imagine an object pool which creates/destroys/manages objects, and when clients ask it for objects, it gives by wrapping the objects in `std::shared_ptr` in such a way that when the shared_ptr goes out of scope, the object held by it returns to the pool (i.e becomes *available* to be used again).... [Contd]. – Nawaz Nov 29 '17 at 09:51
  • [Contd] .. In other words, the smart pointer does **not** have to own the *memory* where the object is created, it may own only the *scope* or a *certain lifetime* during which it's in *acquired* state and once it goes out of scope, the object's state changes to *released*. – Nawaz Nov 29 '17 at 09:51
  • @Nawaz A plausible use case indeed where (1) the deleter is *not* a NOP (2) you have exactly one set of aliased `shared_ptr` for one object – curiousguy Nov 29 '17 at 13:34
  • Exactly... I've implemented `object_pool` *internally* uses `std::unique_ptr` to manage the *memory* and before giving objects to the clients, it wraps the objects in `std::shared_ptr` to enable *automatic* returning to the pool when it gets out of the scope. – Nawaz Nov 29 '17 at 14:13

2 Answers2

1

The raw pointer overloads assume ownership of the pointed-to object. Therefore, constructing a shared_ptr using the raw pointer overload for an object that is already managed by a shared_ptr, such as by shared_ptr(ptr.get()) is likely to lead to undefined behavior, even if the object is of a type derived from std::enable_shared_from_this. -- http://en.cppreference.com/w/cpp/memory/shared_ptr/shared_ptr

In your case you get to shared pointers that have two different ownership information blocks but always increment the ref count of the first shared pointer instance of the class.

If you remove the 'fake deleter' you'l get a double free problem.

Mihayl
  • 3,821
  • 2
  • 13
  • 32
0

On C++11 (and more generally, before C++17)

the only thing we know is that:

[util.smartptr.enab] 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.

Returns: A shared_ptr object r that shares ownership with p.

Postconditions: r.get() == this.

that if read literally, implies that it's unspecified if fc->shared_from_this() returns a copy of rc or fc (although sane implementations will simply assign to the internal weak_ptr once, hence the behaviour should be the same as in the c++17 case, as you observed).


From C++17 on

the situation is clear: enable_shared_from_this is essentially a weak_ptr that gets assigned by the first shared_ptr constructor (or make_shared factory) that sees it's in an expired state.

Hence, rc sets the weak_ptr, fc doesn't, fc->shared_from_this() returns a copy of rc. fc.get() will return a zombie pointer as soon as all rc copies get destroyed. Your observed behaviour is the right one.

Note that all shared_ptr constructors taking a non null pointer (and at most a deleter and/or an allocator) will own the pointer and manage its lifetime, as if it were constructed anew, the only difference being that only the first one will assign to enable_shared_from_this's weak_ptr, if any.

Community
  • 1
  • 1
Massimiliano Janes
  • 5,524
  • 1
  • 10
  • 22