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:
Queue
is templated on the object it accepts- It should be possible to store noncopyable types in the
Queue
- The
Queue
may have multiple implementations. Queue
implementations 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::push
is 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_ptr
topush
? - 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
Queue
interface, 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
Queue
force the user to do so? Why should for exampleQueue
forbid 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.