I’m new to move semantics in C++11 and I don’t know very well how to handle unique_ptr parameters in constructors or functions. Consider this class referencing itself:
#include <memory>
class Base
{
public:
typedef unique_ptr<Base> UPtr;
Base(){}
Base(Base::UPtr n):next(std::move(n)){}
virtual ~Base(){}
void setNext(Base::UPtr n)
{
next = std::move(n);
}
protected :
Base::UPtr next;
};
Is this how I should write functions taking unique_ptr arguments?
And do I need to use std::move in the calling code?
Base::UPtr b1;
Base::UPtr b2(new Base());
b1->setNext(b2); //should I write b1->setNext(std::move(b2)); instead?
Here are the possible ways to take a unique pointer as an argument, as well as their associated meaning.
(A) By Value
In order for the user to call this, they must do one of the following:
To take a unique pointer by value means that you are transferring ownership of the pointer to the function/object/etc in question. After
newBaseis constructed,nextBaseis guaranteed to be empty. You don’t own the object, and you don’t even have a pointer to it anymore. It’s gone.This is ensured because we take the parameter by value.
std::movedoesn’t actually move anything; it’s just a fancy cast.std::move(nextBase)returns aBase&&that is an r-value reference tonextBase. That’s all it does.Because
Base::Base(std::unique_ptr<Base> n)takes its argument by value rather than by r-value reference, C++ will automatically construct a temporary for us. It creates astd::unique_ptr<Base>from theBase&&that we gave the function viastd::move(nextBase). It is the construction of this temporary that actually moves the value fromnextBaseinto the function argumentn.(B) By non-const l-value reference
This has to be called on an actual l-value (a named variable). It cannot be called with a temporary like this:
The meaning of this is the same as the meaning of any other use of non-const references: the function may or may not claim ownership of the pointer. Given this code:
There is no guarantee that
nextBaseis empty. It may be empty; it may not. It really depends on whatBase::Base(std::unique_ptr<Base> &n)wants to do. Because of that, it’s not very evident just from the function signature what’s going to happen; you have to read the implementation (or associated documentation).Because of that, I wouldn’t suggest this as an interface.
(C) By const l-value reference
I don’t show an implementation, because you cannot move from a
const&. By passing aconst&, you are saying that the function can access theBasevia the pointer, but it cannot store it anywhere. It cannot claim ownership of it.This can be useful. Not necessarily for your specific case, but it’s always good to be able to hand someone a pointer and know that they cannot (without breaking rules of C++, like no casting away
const) claim ownership of it. They can’t store it. They can pass it to others, but those others have to abide by the same rules.(D) By r-value reference
This is more or less identical to the “by non-const l-value reference” case. The differences are two things.
You can pass a temporary:
You must use
std::movewhen passing non-temporary arguments.The latter is really the problem. If you see this line:
You have a reasonable expectation that, after this line completes,
nextBaseshould be empty. It should have been moved from. After all, you have thatstd::movesitting there, telling you that movement has occurred.The problem is that it hasn’t. It is not guaranteed to have been moved from. It may have been moved from, but you will only know by looking at the source code. You cannot tell just from the function signature.
Recommendations
unique_ptr, take it by value.unique_ptrfor the duration of that function’s execution, take it byconst&. Alternatively, pass a&orconst&to the actual type pointed to, rather than using aunique_ptr.&&. But I strongly advise against doing this whenever possible.How to manipulate unique_ptr
You cannot copy a
unique_ptr. You can only move it. The proper way to do this is with thestd::movestandard library function.If you take a
unique_ptrby value, you can move from it freely. But movement doesn’t actually happen because ofstd::move. Take the following statement:This is really two statements:
(note: The above code does not technically compile, since non-temporary r-value references are not actually r-values. It is here for demo purposes only).
The
temporaryis just an r-value reference tooldPtr. It is in the constructor ofnewPtrwhere the movement happens.unique_ptr‘s move constructor (a constructor that takes a&&to itself) is what does the actual movement.If you have a
unique_ptrvalue and you want to store it somewhere, you must usestd::moveto do the storage.