std::shared_ptr's secret constructor
Friday, 24 July 2015
std::shared_ptr
has a secret: a constructor that most users don't even know
exists, but which is surprisingly useful. It was added during the lead-up to the
C++11 standard, so wasn't in the TR1 version of shared_ptr
, but it's been
shipped with gcc since at least gcc 4.3, and with Visual Studio since Visual
Studio 2010, and it's been in Boost since at least v1.35.0.
This constructor doesn't appear in most tutorials on std::shared_ptr
. Nicolai
Josuttis devotes half a page to this constructor in the second edition of The
C++ Standard Library, but Scott Meyers doesn't even mention it in his item on
std::shared_ptr
in Effective Modern C++.
So: what is this constructor? It's the aliasing constructor.
Aliasing shared_ptr
s
What does this secret constructor do for us? It allows us to construct a new
shared_ptr
instance that shares ownership with another shared_ptr
, but which
has a different pointer value. I'm not talking about a pointer value that's just
been cast from one type to another, I'm talking about a completely different
value. You can have a shared_ptr<std::string>
and a shared_ptr<double>
share
ownership, for example.
Of course, only one pointer is ever owned by a given set of shared_ptr
objects, and only that pointer will be deleted when the objects are
destroyed. Just because you can create a new shared_ptr
that holds a different
value, you don't suddenly get the second pointer magically freed as well. Only
the original pointer value used to create the first shared_ptr
will be
deleted.
If your new pointer values don't get freed, what use is this constructor? It
allows you to pass out shared_ptr
objects that refer to subobjects and keep
the parent alive.
Sharing subobjects
Suppose you have a class X
with a member that is an instance of some class Y
:
struct X{
Y y;
};
Now, suppose you have a dynamically allocated instance of X
that you're
managing with a shared_ptr<X>
, and you want to pass the Y
member to a
library that takes a shared_ptr<Y>
. You could construct a shared_ptr<Y>
that
refers to the member, with a do-nothing deleter, so the library doesn't actually
try and delete
the Y
object, but what if the library keeps hold of the
shared_ptr<Y>
and our original shared_ptr<X>
goes out of scope?
struct do_nothing_deleter{
template<typename> void operator()(T*){}
};
void store_for_later(std::shared_ptr<Y>);
void foo(){
std::shared_ptr<X> px(std::make_shared<X>());
std::shared_ptr<Y> py(&px->y,do_nothing_deleter());
store_for_later(py);
} // our X object is destroyed
Our stored shared_ptr<Y>
now points midway through a destroyed object, which
is rather undesirable. This is where the aliasing constructor comes in: rather
than fiddling with deleters, we just say that our shared_ptr<Y>
shares
ownership with our shared_ptr<X>
. Now our shared_ptr<Y>
keeps our X
object
alive, so the pointer it holds is still valid.
void bar(){
std::shared_ptr<X> px(std::make_shared<X>());
std::shared_ptr<Y> py(px,&px->y);
store_for_later(py);
} // our X object is kept alive
The pointer doesn't have to be directly related at all: the only requirement is
that the lifetime of the new pointer is at least as long as the lifetime of the
shared_ptr
objects that reference it. If we had a new class X2
that held a
dynamically allocated Y
object we could still use the aliasing constructor to
get a shared_ptr<Y>
that referred to our dynamically-allocated Y
object.
struct X2{
std::unique_ptr<Y> y;
X2():y(new Y){}
};
void baz(){
std::shared_ptr<X2> px(std::make_shared<X2>());
std::shared_ptr<Y> py(px,px->y.get());
store_for_later(py);
} // our X2 object is kept alive
This could be used for classes that use the pimpl idiom, or trees where you
want to be able to pass round pointers to the child nodes, but keep the whole
tree alive. Or, you could use it to keep a shared library alive as long as a
pointer to a variable stored in that library was being used. If our class X
loads the shared library in its constructor and unloads it in the destructor,
then we can pass round shared_ptr<Y>
objects that share ownership with our
shared_ptr<X>
object to keep the shared library from being unloaded until all
the shared_ptr<Y>
objects have been destroyed or reset.
The details
The constructor signature looks like this:
template<typename Other,typename Target>
shared_ptr(shared_ptr<Other> const& other,Target* p);
As ever, if you're constructing a shared_ptr<T>
then the pointer p
must be
convertible to a T*
, but there's no restriction on the type of Other
at
all. The newly constructed object shares ownership with other
, so
other.use_count()
is increased by 1, and the value returned by get()
on the
new object is static_cast<T*>(p)
.
There's a slight nuance here: if other
is an empty shared_ptr
, such as a
default-constructed shared_ptr
, then the new shared_ptr
is also empty, and
has a use_count()
of 0, but it has a non-NULL
value if p
was not
NULL
.
int i;
shared_ptr<int> sp(shared_ptr<X>(),&i);
assert(sp.use_count()==0);
assert(sp.get()==&i);
Whether this odd effect has any use is open to debate.
Final Thoughts
This little-known constructor is potentially very useful for passing around
shared_ptr
objects that reference parts of a non-trivial data structure and
keep the whole data structure alive. Not everyone will need it, but for those
that do it will avoid a lot of headaches.
Posted by Anthony Williams
[/ cplusplus /] permanent link
Tags: shared_ptr, cplusplus
Stumble It! | Submit to Reddit | Submit to DZone
If you liked this post, why not subscribe to the RSS feed or Follow me on Twitter? You can also subscribe to this blog by email using the form on the left.
Design and Content Copyright © 2005-2024 Just Software Solutions Ltd. All rights reserved. | Privacy Policy
7 Comments
This is great! I'm designing an arena based allocator and needed something like this exactly; now I've got a neat way to create a shared pointer that increments the arena itself rather than having to use emplace_shared all over the place.
I've known about this, but for some reason not understood its usefulness. Thanks for a good explanation.
Le hacker news army reporting in!
Le hacker news army reporting in!
JUST U S T
An example of how std::shared_ptr aliasing constructor can be used instead of std::enable_shared_from_this: http://stackoverflow.com/a/27925567 (second piece of code in the answer).
I've found this technique quite useful for my code.
Thanks Anthony! I didn't know about this - but it's very useful. It's been one of the only remaining reasons I've been keeping my own shared pointer implementation around in our code-base. I can possibly go completely std on our pointers now :-)