What is shared_ptr's aliasing constructor for?
Simple example:
struct Bar {
// some data that we want to point to
};
struct Foo {
Bar bar;
};
shared_ptr<Foo> f = make_shared<Foo>(some, args, here);
shared_ptr<Bar> specific_data(f, &f->bar);
// ref count of the object pointed to by f is 2
f.reset();
// the Foo still exists (ref cnt == 1)
// so our Bar pointer is still valid, and we can use it for stuff
some_func_that_takes_bar(specific_data);
Aliasing is for when we really want to point to Bar
, but we also don't want the Foo
to get deleted out from under us.
As Johannes points out in the comments, there is a somewhat equivalent language feature:
Bar const& specific_data = Foo(...).bar;
Bar&& also_specific_data = Foo(...).bar;
We're taking a reference to a member of a temporary, but the temporary Foo
is still kept alive as long as specific_data
is. As with the shared_ptr
example, what we have is a Bar
whose lifetime is tied to a Foo
- a Foo
that we cannot access.
shared_ptr aliasing constructor
Example:
#include <iostream>
#include <iomanip>
struct some_type
{
int i;
};
void my_deleter(some_type* p)
{
std::cout << "my_deleter called!" << std::endl;
delete p;
}
#include <memory>
int main()
{
std::shared_ptr<int> pm;
{
// Note: better use make_shared
auto x = new some_type;
// create a shared_ptr that owns x and a deleter
std::shared_ptr<some_type> r(x, &my_deleter);
std::cout << r.use_count() << std::endl;
// share ownership of x and the deleter with pm
pm = std::shared_ptr<int>(r, &r->i);
std::cout << r.use_count() << std::endl;
// r gets destroyed
}
std::cout << pm.use_count() << std::endl;
std::cout << "get_deleter == 0? " << std::boolalpha
<< (nullptr == std::get_deleter<decltype(&my_deleter)>(pm))
<< std::endl;
}
Output:
1
2
1
get_deleter == 0? false
my_deleter called!
N.B. I can't compile this example with a free function my_deleter
, there's some casting error for the free get_deleter
function (trying to cast from void*
to a function pointer type with a static_cast
).
Aliasing ctor:
[util.smartptr.shared.const]/13-14
template<class Y> shared_ptr(const shared_ptr<Y>& r, T *p) noexcept;
13 Effects: Constructs a
shared_ptr
instance that storesp
and shares ownership withr
.14 Postconditions:
get() == p && use_count() == r.use_count()
Ctor with user-provided deleter:
[util.smartptr.shared.const]/9
template shared_ptr(Y* p, D d);
Effects: Constructs a
shared_ptr
object that owns the objectp
and the deleterd
.
Dtor:
[util.smartptr.shared.dest]/1
~shared_ptr();
1 Effects:
- If
*this
is empty or shares ownership with anothershared_ptr
instance (use_count() > 1
), there are no side effects.- Otherwise, if
*this
owns an objectp
and a deleterd
,d(p)
is called.- Otherwise,
*this
owns a pointerp
, anddelete p
is called.
Combining those (let's skip the assignment operators):
- The
shared_ptr
instancer
owns both the object and the deleter. - The aliasing ctor lets the new
shared_ptr
instance share ownership withr
(i.e. for both, the object and the deleter). - When the dtor of this new instance is called (or an assignment operator),
- If
use_count > 1
, no effects. - Else, this instance owns the object which
r
pointed to and the deleter (if any) and will either use this deleter (if it exists) ordelete
on the object pointed to.
- If
Understanding the prototype of shared_ptr aliasing constructor
You are confusing different template parameters/arguments. _Yp
is not the template parameter we are instantiating the shared_ptr
with. The parameter of the whole shared_ptr
template in GCC's implementation is called _Tp
, not _Yp
. Inside shared_ptr
that _Tp
is also known as element_type
.
Meanwhile, _Yp
is a parameter of a nested member template, which is constructor template.
shared_ptr
itself and its constructor template are two "orthogonal" templates. _Tp
and _Yp
are two independent and unrelated template parameters.
You don't (and can't) explicitly specify the argument for _Yp
. It will be deduced automatically. But you have to specify the argument for _Tp
, which is exactly what you see in your example
shared_ptr<int> pi(pii, &pii->first);
^ ^
| |
| The `_Yp` parameter is kinda/sorta implicitly present here.
| It parametrizes the constructor template. C++ has no syntax
| for specifying it explicitly
|
This is `_Tp`, not `_Yp`. `_Tp` parametrizes
the whole `shared_ptr` template
The _Tp
in this example is specified as int
, as it should be. The _Yp
is deduced from pii
as pair<int,int>
, exactly as you expected it to be.
Using C++ shared pointer's aliasing constructor with an empty shared pointer
As you already know, with your current solution, p
has a use_count()
of zero, that's why the weak_ptr
is expired. This seems to be ok, according to the C++ draft N4296:
20.8.2.2.1 shared_ptr constructors [util.smartptr.shared.const]
template shared_ptr(const shared_ptr& r, T* p) noexcept;
13 Effects: Constructs a shared_ptr instance that stores p and shares ownership with r.
14 Postconditions: get() == p && use_count() == r.use_count()
15 [ Note: To avoid the possibility of a dangling pointer, the user of this constructor must ensure that p
remains valid at least until the ownership group of r is destroyed. — end note ]
16 [ Note: This constructor allows creation of an empty shared_ptr instance with a non-null stored
pointer. — end note ]20.8.2.2.2 shared_ptr destructor [util.smartptr.shared.dest]
~shared_ptr();
1 Effects:
(1.1) — If *this is empty or shares ownership with another shared_ptr instance (use_count() > 1),
there are no side effects.
(1.2) — Otherwise, if *this owns an object p and a deleter d, d(p) is called.
(1.3) — Otherwise, *this owns a pointer p, and delete p is called
emphasis mine.
You could use the following instead which gives a shared_ptr
with a use_count()
of one:
std::shared_ptr<int> p(&global, [](int*){});
This uses an empty custom deleter.
Why doesn't the shared pointer aliasing constructor use pass-by-value semantics
You are missing the destructor. your suggested implementation will decrement the count upon return from constructor (the automatic shared_ptr
instance will be destructed). So if the compiler is not able to optimize to noop, you just pay the cost of two extra atomic operation for no gain and an added logical error that will backlash with a UB.
Regardless of the devised semantics, the constructor must increment the count. However, your proposed solution -after fixing the bug you've introduced- combines the implementation of both r-value and l-value versions of the constructor in a by-value constructor, discarding the mandatory atomic access optimization imposed by current implementation.
Is alias construction of shared_ptr from void safe?
From https://en.cppreference.com/w/cpp/memory/shared_ptr/shared_ptr:
... such as in the typical use cases where ptr is a member of the object managed by r or is an alias (e.g., downcast) of r.get() ...
So this is exactly the envisioned use case.
How to implement the aliasing constructor by own for my own shared_ptr implementation?
Boost solves this problem by either using template member friends or making the members public, depending on the value of the define BOOST_NO_MEMBER_TEMPLATE_FRIENDS
.
For example the friend declaration looks like this:
template<class Y> friend class shared_ptr;
template<class Y> friend class weak_ptr;
How would one specify a custom deleter for a shared_ptr constructed with an aliasing constructor?
It doesn't really make sense to add a custom deleter in the construction of the aliasing shared_ptr
.
The deleter is associated with the managed object, not the specific shared_ptr
instance that will actually call it. The deleter is stored together with the object in the shared control block.
So once all aliasing and non-aliasing shared_ptr
instances to the shared object are destroyed, the last instance (whether it is aliasing or not) will call the deleter which was associated with the object when it was first put under shared_ptr
control.
So to add a custom deleter for the Foo
object, replace std::make_shared
:
auto f = std::shared_ptr<Foo>(new Foo(some, args, here), some_deleter_here);
some_deleter_here
will then be called with the Foo
pointer as argument, even if the aliasing shared_ptr
is the last to be destroyed.
Note however that if you are using a custom deleter, then you probably are not creating the pointer with a simple call to new
. The only correct way to delete an object created with new
is to call delete
on it, which is what the default deleter already does.
Related Topics
Lightweight Memory Leak Debugging on Linux
Debugging in Linux Using Core Dumps
While (1) VS. for (;;) Is There a Speed Difference
Find Out If String Ends with Another String in C++
How to Enable C++11 in Qt Creator
How to Find What Version of Libstdc++ Library Is Installed on Your Linux MAChine
Headers Including Each Other in C++
Returning Temporary Object and Binding to Const Reference
How to Increase Thread Priority in Pthreads
What Does the Q_Object MACro Do? Why Do All Qt Objects Need This MACro
Is There a Decent Wait Function in C++
Why Does C++ Need the Scope Resolution Operator