std::shared_ptr #
Consider the following code below:
struct Base {
virtual ~Base() {}
};
struct Derived : public Base {
virtual ~Derived() {}
};
struct Wrapper {
Wrapper(const std::shared_ptr<Base>&a) : a(a) {}
const std::shared_ptr<Base>& a;
};
Wrapper do_something(const std::shared_ptr<Base>& a [[clang::lifetimebound]]) {
Wrapper w(a);
return w;
}
int main() {
std::shared_ptr<Derived> t = std::make_shared<Derived>();
auto i = do_something(t);
std::printf("%p\n", &t);
std::printf("%p\n", &(i.a));
}
Everythings seems OK but if we use the annotation [[clang::lifetimebound]]
from clang, we could get the following warning:
warning: temporary whose address is used as value of local variable ‘i’ will be destroyed at the end of the full-expression [-Wdangling]
But if we just ignore the warning and run the program, we may get the output like this:
0x7ffff09985d0
0x7ffff09985b8
So here comes some questions: What happened here? Where is the “temporary” exactly as? And why are the addresses of t
and i.a
different?
I believe we all know that if we use a temporary reference as the parameter, when the object is destroyed, the reference would turn into a dangling pointer. However, t
wasn’t destoryed. Nonetheless, compiler gives us a warning that we take a temporary variable (which was t
) which was destoryed at the end of the full-expression. That’s weird, because t
was exactly there and wasn’t destoryed! Some people may say that’s a compiler bug. Well, maybe the warning was strange, but it did generate a correct warning.
So, let’s take a see at constructor of std::shared_ptr from cppreference.
shared_ptr( const shared_ptr& r ) noexcept;
template< class Y >
shared_ptr( const shared_ptr<Y>& r ) noexcept;
Constructs a shared_ptr which shares ownership of the object managed by r. If r manages no object,
*this
manages no object either. The template overload doesn’t participate in overload resolution if Y* is not implicitly convertible to(until C++17)compatible with(since C++17) T*.
As we know, Base
and Derived
are covariant and raw pointers to them will act accordingly. But shared_ptrdo_something
. At the end of the expression auto i = do_something(t);
, the shared_ptrWrapper
in do_something
will become a dangling pointer. And that is where the temporary variable comes from.
However, the above scenario does not occur with unique_ptr. (What can I say)
std::unique_ptr<Derived>
is implicitly convertible tostd::unique_ptr<Base>
through the overload (6) (because both the managed pointer and std::default_delete are implicitly convertible).
Hmm. Keep updating…