Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Returning unique_ptr<Object> as unique_ptr<const Object>

Tags:

I have a method like this:

std::unique_ptr<const Stats> Table::GetStats() const {
  std::unique_ptr<Stats> result;
  // ...
  // Prepare stats. Under some conditions an exception may be thrown.
  // ...
  return result;
}

The problem is that it doesn't compile:

error: cannot bind ‘std::unique_ptr’ lvalue to ‘std::unique_ptr&&’

I can make it compile by using the following bypass:

return std::unique_ptr<const Stats>(result.release());

But it seems a bit as doing something excessive. I cannot understand, what's wrong with the first piece of code from C++'s point of view? Is there more elegant solution?

like image 228
Alexey Avatar asked May 05 '18 07:05

Alexey


People also ask

What happens when you return a unique_ptr?

If a function returns a std::unique_ptr<> , that means the caller takes ownership of the returned object. class Base { ... }; class Derived : public Base { ... }; // Foo takes ownership of |base|, and the caller takes ownership of the returned // object.

Can unique_ptr be copied?

A unique_ptr does not share its pointer. It cannot be copied to another unique_ptr , passed by value to a function, or used in any C++ Standard Library algorithm that requires copies to be made. A unique_ptr can only be moved.

What happens when unique_ptr goes out of scope?

std::unique_ptr is a smart pointer that owns and manages another object through a pointer and disposes of that object when the unique_ptr goes out of scope. The object is disposed of, using the associated deleter when either of the following happens: the managing unique_ptr object is destroyed.

Can you pass a unique_ptr?

Because the unique pointer does not have a copy constructor. Hence you cannot pass it by value, because passing by value requires making a copy. Actually that is nearly the sole and whole point of a unique_ptr .


1 Answers

Your code should work fine, because in return statement:

(emphasis mine)

(since C++11)

If expression is an lvalue expression and the conditions for copy elision are met, or would be met, except that expression names a function parameter, then overload resolution to select the constructor to use for initialization of the returned value is performed twice: first as if expression were an rvalue expression (thus it may select the move constructor or a copy constructor taking reference to const), and if no suitable conversion is available, overload resolution is performed the second time, with lvalue expression (so it may select the copy constructor taking a reference to non-const).

The above rule applies even if the function return type is different from the type of expression (copy elision requires same type)

That means, even result is an lvalue, it'll be regarded as an rvalue at first, then the following constructor would be selected, it could convert std::unique_ptr<Stats> to std::unique_ptr<const Stats>.

template< class U, class E >
unique_ptr( unique_ptr<U, E>&& u ) noexcept;

It seems gcc4.9.2 doesn't behave this way (i.e. treating the expression as an rvalue expression firstly); gcc 9 just works fine.

As @RichardHodges commented, you could use std::move as the workaround.

like image 194
songyuanyao Avatar answered Sep 28 '22 04:09

songyuanyao