Reputation: 30577
I was updating some old code that uses auto_ptr
to use unique_ptr
instead. It was mostly a search and replace job, but I found I was getting a compilation error in one place where the code returns a unique_ptr
.
Here is an example that illustrates the issue:
#include <string>
#include <iostream>
#include <memory>
#include <utility>
using namespace std;
struct Foo {
unique_ptr<string> us;
Foo() {
this->us = unique_ptr<string>(new string("hello"));
}
unique_ptr<string> bar() {
return this->us;
}
};
int main(int argc, const char **argv) {
Foo foo;
unique_ptr<string> s = foo.bar();
cout << *s << endl;
}
When I compile that, I get this:
t1.cpp: In member function ‘std::unique_ptr<std::basic_string<char> > Foo::bar()’:
t1.cpp:17:16: error: use of deleted function ‘std::unique_ptr<_Tp, _Dp>::unique_ptr(const std::unique_ptr<_Tp, _Dp>&) [with _Tp = std::basic_string<char>; _Dp = std::default_delete<std::basic_string<char> >]’
return this->us;
^~
In file included from /opt/rh/devtoolset-7/root/usr/include/c++/7/memory:80:0,
from t1.cpp:4:
/opt/rh/devtoolset-7/root/usr/include/c++/7/bits/unique_ptr.h:388:7: note: declared here
unique_ptr(const unique_ptr&) = delete;
^~~~~~~~~~
If I alter the line with the error to specify a move like this:
return move(this->us);
then it works.
I have found multiple references indicating that a move should not be required - for example this SO question, and these guidelines from the chromium project.
My question is: why does the move need to be explicitly specified in this case? Is it related to fact that I am returning the value of an instance variable somehow?
Apologies in advance if this is a dupe - I feel sure it would have been asked before, but I struggle to come up with search terms that find it.
Upvotes: 5
Views: 7099
Reputation: 8284
This shows the exact danger of auto_ptr and why it was removed.
If you imagine calling the function twice you can see what the issue is.
auto_ptr<string> Foo::bar() {
return this->us;
}
int func() {
Foo foo;
auto_ptr<string> a = foo.bar();
auto_ptr<string> b = foo.bar();//uh oh, null ptr
}
If you like this behavior, you can emulate it very easily with unique_ptr
#include <string>
#include <iostream>
#include <memory>
#include <utility>
using namespace std;
struct Foo {
unique_ptr<string> us;
Foo() {
this->us = unique_ptr<string>(new string("hello"));
}
unique_ptr<string> bar() {
unique_ptr<string> local_us(us.release());
return local_us;
}
};
int main(int argc, const char **argv) {
Foo foo;
unique_ptr<string> a = foo.bar();
unique_ptr<string> b = foo.bar();//b is a nullptr
cout << *s << endl;
}
Upvotes: 2
Reputation: 36379
Used correctly std::auto_ptr
has the same purpose as std::unique_ptr
, it holds a unique reference to a single object which is deleted on destruction of the pointer.
As std::auto_ptr
predates move semantics it has a copy constructor which behaves like a move constructor. It is very easy to accidentally copy a std::auto_ptr
and not realise your original std::auto_ptr
is now empty.
As std::unqiue_ptr
is only moveable and not copyable this situation is avoided. You have to explicitly give up your ownership of the pointer using std::move
.
The exception to this is with local stack allocated std::unique_ptr
s, these would be destroyed at the end of the current scope anyway so the language allows you to return a std::unique_ptr
without using std::move
. As your us
variable is not a local variable you aren't allowed to return it directly to protect you from accidentally releasing ownership.
Upvotes: 2
Reputation: 136208
You are trying to return Foo::us
member variable of type std::unique_ptr<>
. The sole purpose of std::unique_ptr<>
is to be non-copyable but moveable. This is why that return requires std::move
which resets Foo::us
member variable to null
.
bar
function should probably return that std::string
by value or a const reference:
string const& bar() const {
return *this->us;
}
And then:
int main() {
Foo foo;
string const& s = foo.bar();
cout << s << endl;
}
Upvotes: 3
Reputation: 73366
It's all about semantics. A unique_ptr
is a unique ownership, so it would be either returned or in us
, but not both. The rules of the standard fortunately protect you against this misunderstanding.
What you can do is return a temporary unique_ptr
(which will be moved):
unique_ptr<string> bar() {
return make_unique<string>("hello, world");
}
But if you need a copy of the unique_ptr
in several places, which seems to be the case according to your main()
, you are not looking for a unique but a shared ownership. So you should go to a shared_ptr
:
struct Foo {
shared_ptr<string> us;
Foo() {
us = make_shared<string>("hello, world");
}
shared_ptr<string> bar() {
return us;
}
};
int main(int argc, const char **argv) {
Foo foo;
auto s = foo.bar();
cout << *s << endl;
}
Upvotes: 9
Reputation: 170045
The references you found all mention that the object returned must be a function local variable. You return a member. Member variables will not be implicitly treated as rvalues when returned.
It's not without reason. A class usually doesn't want to lose ownership of its resources unless done explicitly.
A function local is about to die anyway when the function returns, so it's a worthwhile optimization to let the compiler implicitly cannibalize it.
Upvotes: 8