Reputation: 1748
I came across a weird problem. I get an error message in setGetDog() method when written this way:
class Dog
{
public:
void bark() { std::cout << "Bark!" << std::endl; }
};
class test
{
public:
std::unique_ptr<Dog> setGetDog()
{
if (!dog_)
{
dog_ = std::make_unique<Dog>();
}
return dog_;
}
private:
std::unique_ptr<Dog> dog_;
};
int main()
{
auto testClass = std::make_unique<test>();
auto dog = testClass->setGetDog();
}
Error is like this:
error: use of deleted function 'std::unique_ptr<_Tp, _Dp>::unique_ptr(const std::unique_ptr<_Tp, _Dp>&) [with _Tp = Dog; _Dp = std::default_delete<Dog>]'
But when I change the implementation to:
std::unique_ptr<Dog> setGetDog()
{
return std::make_unique<Dog>();
}
It works just fine.
What is the matter with dog_
?
I do not understand why the error states that it is deleted when in fact test
is still alive when the method was called (and so dog_
as well).
I know I can write this some other way but I'm specifically curious on why behavior is like this on this implementation.
Can anyone please enlighten me?
Upvotes: 1
Views: 336
Reputation: 180415
There are a few different types of value categories/situations that objects have that affects how there are returned from a function. Lets look at the rvalue category first. When you do
std::unique_ptr<Dog> setGetDog()
{
return std::make_unique<Dog>();
}
std::make_unique<Dog>()
returns by value making the return object an rvalue. That means that when the compiler returns the object it will implicitly move it. (the compiler can also elide the copy but that doesn't matter for this discussion) Since the object is moved you don't have any issues since std::unique_ptr
is movable.
The second case is when you have an lvalue, but it is a function local object. If you had
std::unique_ptr<Dog> setGetDog()
{
auto ptr = std::make_unique<Dog>();
return ptr;
}
Here ptr
is an lvalue but it is going away so we can call it an xvalue(expiring value). With xvalues, when you return them the compiler tries to move it, since moving it is a safe operation in this case. If the move operation does not exist/is not viable, then the compiler falls back to copying. Since std::unique_ptr
is movable, it gets moved without error.
The last case is when you have a plain lvalue like you have with
class test
{
public:
std::unique_ptr<Dog> setGetDog()
{
if (!dog_)
{
dog_ = std::make_unique<Dog>();
}
return dog_;
}
private:
std::unique_ptr<Dog> dog_;
};
Here, dog_
is a member of the class, not a object local to the function. This means the only thing the compiler can do is try and make a copy. Since you can't copy a unique_ptr
, you get an error. You would need return std::move(dog_);
for it to compile, but if you did that dog_
in the class would be empty.
Upvotes: 3
Reputation: 21609
When you just return the unique_ptr
then return value optimization can be applied. This is because the unique_ptr
can actually be constructed at the address of the unique_ptr instance in the calling function. The compiler allows this as no copying is occurring.
When you add the if, the function may or may not be trying to return an existing instance. It's a runtime decision. An existing instance wont have the opportunity to be constructed where the calling function wants it and so must be copied.
unique_ptr
has a deleted copy constructor to preserve the unique part of its contract, so the code does not compile.
Depending on your needs you may want to use a shared_ptr
, use std::move
(mistake probably, it empties the unique_ptr
inside your class) or use value semantics to copy the actual object and not the pointer to it.
Upvotes: 1