Is a std::unique_ptr cleaned up automatically after scope when given as reference?

Viewed 831

It is possible to return with a pointer for a function, and this is useful for lots of reasons, but is it suggested to take reference from that return value?

#include <iostream>
#include <memory>

using namespace std;

unique_ptr<int> give_a_number(){
    return std::make_unique<int>(6);
}

int main(){
   int& ret = *give_a_number();
   return ret;
}

According to the sanitizer, no leaks occur :

user@comp: Scrapbook$ g++ main.cpp -fsanitize=address -g
user@comp: Scrapbook$ 

So the reference is cleaned up somehow, but I fail to understand why. How does the clean-up happen, and even how the unque_ptr is able to keep track of the data. Should this be consdered as safe behavior? Is this an accepted usage in the community?

2 Answers

There is no leak here. In general an object owned by a std::unique_ptr is never leaked, as long as .release() is not called on the owning std::unique_ptr.

Your program does however have undefined behavior because the lifetimes are not correct. If you store a reference or pointer to the owned object you take over some responsibility to assure correct object lifetimes.

give_a_number() returns, by-value, a std::unique_ptr owning the int object. This std::unique_ptr is therefore materialized as a temporary object in the statement

int& ret = *give_a_number();

You don't move this temporary into any persistent std::unique_ptr instance. So when the temporary object is destroyed at the end of the statement, the int it owns is destroyed as well. Now your reference is dangling.

You then use the dangling reference in return ret;, causing undefined behavior.

You can store a reference to the owned object, but if you do so, you must assure that the std::unique_ptr instance owning the reference out-lives the int reference to avoid lifetime issues. E.g. the following are fine:

int main(){
   return *give_a_number(); // `std::unique_ptr` temporary outlives return value initialization
}
int main(){
   auto ptr = give_a_number(); // `ptr` lives until `main` returns
   int& ret = *ptr;
   return ret;
}

Usually it is safer to just store the smart pointer in an automatic variable as above and obtain the object by dereferencing with *ptr, where needed. The std::unique_ptr and the object it owns then live until the end of the block, if it is not moved from.

But it is perfectly correct to e.g. pass *ptr to a function by-reference. Since ptr outlives such a function call, there will not be any problem.

This also works if you pass *give_a_number() directly as argument to a function, because the std::unique_ptr temporary will outlive such a call as well. But in that case the std::unique_ptr and the object it owns will live only until the end of the statement (full-expression), not the end of the block.

unique_ptr lives only in an expression with give_a_number. Pointer to int is destroyed before return statement.

One may see how it behaves by using non-POD A with logging destructor and constructor calls.

struct A
{
  A() { std::cout << "A created\n"; }
  ~A() { std::cout << "A destroyed\n"; }
};

unique_ptr<A> give_a_number(){
  return std::unique_ptr<A>(new A());
}

int main(){
  std::cout << "start\n";
  A& ret = *give_a_number();
  std::cout << "reference to removed object here\n";
  return 0;
}

Output is:

start
A created
A destroyed
reference to removed object here

I don't know why sanitizer does not print alert in your case because mine does when called on your code. I use g++ 5.3.1

==21820==ERROR: AddressSanitizer: heap-use-after-free on address 0x60200000eff0 at pc 0x000000400bd5 bp 0x7ffee5f3cac0 sp 0x7ffee5f3cab8
READ of size 4 at 0x60200000eff0 thread T0
Related