This question discusses passing by value vs. passing by rvalue reference in C++. However I find the answers unsatisfactory and not entirely correct.
Let's say I want to define a Queue abstract interface:
Queueis templated on the object it accepts- It should be possible to store noncopyable types in the
Queue - The
Queuemay have multiple implementations. Queueimplementations should not be precluded from the opportunity of avoiding copies, if they can avoid them.
I wish for my interface to express the intent that calling Queue<T>::push(value) implies the "transfer" of value to the queue. By "transfer" I mean that:
Queue::pushis allowed to do whatever it wants with value, including modifying it, and the user should not be affected.- After a call to
Queue::push, if the user uses value, it should not have side effects for theQueue.
My options for the Queue interface are:
Option 1:
template<typename T>
class Queue {
public:
virtual void push(const T& value) = 0;
};
Option 2:
template<typename T>
class Queue {
public:
virtual void push(T&& value) = 0;
};
Option 3:
template<typename T>
class Queue {
public:
virtual void push(T value) = 0;
};
The C++ core guidelines don't help much:
- F.16 says "For “in” parameters, pass cheaply-copied types by value and others by reference to const". Here "value" is an in parameter, so the guidelines say I should pass it by reference to const. But this requires "T" to be copyable, which is an unnecessary restriction. Why shouldn't I be able to pass a
std::unique_ptrtopush? - F.18 says that "For “will-move-from” parameters, pass by X&& and std::move the parameter". First of all, what is a “will-move-from” parameter? Secondly, as I'm defining the
Queueinterface, I have no idea of what the implementation will do. It may move the object, or it may not. I don't know.
So what should I do?
- Option 1: this does not express the right semantic IMO. With this option the code
std::unique_ptr<int> ptr; queue.push(std::move(ptr));does not work, but there's no reason why it shouldn't. - Option 2: this should have the right semantic, but it forces the user to either move or copy explicitly the value. But why should
Queueforce the user to do so? Why should for exampleQueueforbid the following code?std::shared_ptr<int> ptr; queue.push(ptr); - Option 3: it allows to push a copy of a value, or to "move-in" a value. So
std::shared_ptr<int> ptr; queue.push(ptr);is valid, and so isstd::unique_ptr<int> ptr; queue.push(std::move(ptr));The required semantic holds. However, nothing stops the user from callingStorage<std::vector<int>>::store(veryLargeVector), which may cause a large, unnecessary copy.