Consider this code:
class A : public std::enable_shared_from_this<A>
{
public:
std::shared_ptr<A> f()
{
return shared_from_this();
}
};
int main()
{
A a;
std::shared_ptr<A> ptr = a.f();
}
This code terminated in Visual Studio 2017. I guess I am doing something wrong here. Can anyone help me with this? I want a shared_ptr on a created by shared_from_this().
You cannot use
shared_from_this
to generate a new shared pointer. You already have to have an existing shared pointer to get a new one:Because
a
is not a owned by a shared pointer. From cppreference:The issue is fundamental in the design (not technical details)
Whatever the exact specification of that C++ standard version you are using, what you are trying to do is impossible. Knowing the fine details of the specification of
shared_from_this
isn't needed to conclude that the code contains a design contradiction: just understanding the intent, which is to obtain ashared_ptr<A>
tothis
, inside a member function called ona
, an automatic object, is enough to determine that the design is in error.In fact, any attempt at making an owning smart pointer (incl. but not limited to
unique_ptr
,shared_ptr
) that points to (that owns) an object with "scoped" lifetime, that is an object whose lifetime is defined by the scope of the declaration of the object and ended by exiting something:is a design error, because the:
new
(plainoperator new
ornothrow
variant) that permits the call ofdelete
on the result);std::exit
, orreturn
from main), no matter what (even if your owning smart pointer already took care of it); trying to destroy an already destroy object is not OK.This includes constructing a smart pointer that owns (i.e. that promises to
delete
) a member of a class instance that was dynamically allocated:Here the lifetime of the object pointed to by
p
is managed dynamically; the timing of the destruction of determined explicitly, by program code, and the lifetime of the unique memberm
is intrinsically linked to the lifetime of theA
object; but the member itself need not be destructed explicitly and shall not be deleted. If theOK
preprocessor constant is 1, all is well; if it is 0, you are trying to manage explicitly the lifetime of a member, which is unsound.About the term "explicit" call to
delete
: although thdelete
operator never appears in the code, its call is implicit on the use ofstd::shared_ptr
; in other words,std::shared_ptr
explicitly usesdelete
, so use ofstd::shared_ptr
(or other similar owning smart pointers) are indirect use ofdelete
.Safely sharing ownership with a smart pointer
The only safe way to share ownership of a
shared_ptr
is to make one from anothershared_ptr
, directly, or indirectly. This is the fundamental property ofshared_ptr
: all instances pointing to the one object must be traceable back to the one instance that one constructed with a raw pointer (or alternatively withmake_shared
).This is a direct consequence of the fact that the ownership information (usually a reference count, but could be a linked list if you love inefficient implementations) is not inside the managed object, but inside the information block created by
shared_ptr
. This is not a property of juststd::shared_ptr
, it's a fact of life of all these externally managed objects, without a global registry, it's impossible to find the manager.The basic design decision of these smart pointer is that the managed object need not be modified to use a smart pointer; hence they can be used on existing data type (incl. fundamental types).
Importance of weak copies of a shared owning manager
The fundamental property of
shared_ptr
would create an issue: as every layer of code (that might need to call a function that needs an owning pointer) needs to keep a copy of theshared_ptr
around, this can create a web of owning smart pointers, some of which might reside in an object whose lifetime is managed by another who lifetime is managed by that exact smart pointer; because the smart pointer basic specification is that the managed object is not destructed before all copies of the smart pointer in charge of its destruction are destroyed, these objects would never be destroyed (as specified; this is not a consequence of the particular implementation choice of reference counting). Sometimes a copy of a owning smart pointer of a specie that doesn't prevent influence the lifetime of the managed object is needed, hence the need for the weak smart pointer.A (non null) weak smart pointer is always directly or indirectly a copy of an owning smart pointer, directly or indirectly a copy the original smart pointer that took ownership. That "weak reference" actually is a "strong" owning smart pointer to the information regarding the existence of other owning copies of the smart pointer: as long as there is a weak smart pointer, it will be possible to determine whether there is a live owning smart pointer, and if so to obtain a copy, that is make a shared smart pointer that an exact copy of the original (the lifetime of the original may have ended many generations of copies ago).
The only purpose of a weak smart pointer is to obtain such copies of the original.
The purpose of
std::enable_shared_from_this
The only use of
std::enable_shared_from_this
is to obtain a copy of the originalshared_ptr
; that implies that such owning smart pointer must already exist. No new original (another smart pointer taking ownership) will be made.Only use
std::enable_shared_from_this
for classes that are only intended to be managed by ashared_ptr
.Details of
std::enable_shared_from_this
All that being said about the theoretical principles, it's useful to understand what
std::enable_shared_from_this
contains, how it can produce ashared_ptr
when used correctly (and why it cannot be expected to work in any other case).The "magic" of
std::enable_shared_from_this
may seem mysterious, and too magic so that users don't have to think about it, but it's actually extremely simple: it keeps aweak_ptr
intended to be a copy of the original. Obviously it cannot be constructed as such copy, as the original cannot even be initialized when thestd::enable_shared_from_this
subobject is constructed: a valid owning smart pointer can only refer to a fully constructed object, because it owns it and is in charge of its destruction. [Even if by some cheating an owning smart pointer was made before the managed object was fully constructed, and hence destructible, the owning smart pointer would be a risk of premature destruction (even if during the normal course of events its lifetime is long, it could be shortened by an exception for example).]So data member initialization in
std::enable_shared_from_this
is inherently default initialization: the "weak pointer" is null at that point.Only when the original finally takes ownership if the managed object, it can collude with
std::enable_shared_from_this
: the constructing of the originalshared_ptr
will set once and for all theweak_ptr
member insidestd::enable_shared_from_this
. Active collusion between these components is the only way to make the stuff work.It's still the user's responsibility to only call
shared_from_this
only when it can possibly return a copy of the original, that is, after the original has been constructed.About fake (non owning) owning smart pointers
A fake owning smart pointer is one that does no cleanup ever: owning smart pointer in name only. They are special case of "owning" smart pointers used in such a way that no destruction or cleanup is performed. This ostensibly means that they could be used for objects whose lifetime is predetermined (and long enough) and for which there is a need to have a pretend owning smart pointer; unlike a real owning smart pointer, keeping a copy will not extend the lifetime of the object, so that lifetime should better be really long. (Because a copy of a owning smart pointer could be stored in a global variable, the object could still be expected to be alive after a
return
frommain
.)These non owning owners are obviously a contradiction in the terms and rarely safe (but can be proven safe in a few cases).
They rarely solve a legitimate problem (one that isn't the immediate consequence of a very bad design): a
shared_ptr
in an interface means that the receiver is expecting to be able to extend the lifetime of the managed object.