The question holds for both stacks and queues, but I will just allude to a stack here for simplicity.
Assuming that we push non-const objects into std::stack
, is it safe, when we're popping from the stack, to move the object at the top of the stack into a temporary variable before popping like in the following:
std::stack<std::string> st;
st.emplace("asdf");
auto popped = std::move(st.top());
st.pop();
Yes, it is safe, if you use the non-const version of the stack. (For a const version of the stack, you will most likely copy the object or get a compile error)
Since std::stack
returns a non-const reference to an object, you are free to modify it. This includes moving out from the object.
Recall that moving from an object keeps it in a valid state (at least, if the class is implemented correctly). It does not destroy it. It is still there on the top of your stack. It just doesn't contain any definite value. The follow-up pop
will call a proper destructor on it without a problem.
Note, that the moving-out is not allowed for std::priority_queue
, because that kind of container actually cares about the contents. And - for that reason it just returns a const-reference, which cannot be used to move things out from it.
In response to Iamanon's observation that std::move
can be perfomed on a const reference. In fact you can do that. Usually it is not useful though and it will usually reduce to a copy, instead of move. Consider the following example:
#include <iostream>
#include <string>
#include <stack>
class Foo {
public:
Foo() {
std::cout << "created\n";
}
~Foo() {
std::cout << "destroyed " << value << "\n";
}
Foo(const Foo& other) : value(other.value) {
std::cout << "copied\n";
}
Foo(Foo&& other) : value(other.value) {
other.value++;
std::cout << "moved\n";
}
Foo(const Foo&& other) : value(other.value) {
std::cout << "const-moved\n";
}
int value = 0;
};
int main()
{
std::stack<Foo> st;
st.emplace();
const std::stack<Foo>& cst = st;
auto popped = std::move(cst.top());
st.pop();
}
If you run the above, a const-moved version will be used. However, as you implement your const-move constructor you will realize that you cannot really do much better than your regular copy constructor. That is because other
remain immutable. For example, you cannot take ownership of anything other
holds, and reset it within other
.
And if you remove the const-move constructor, the compiler will use the regular copy constructor.
If the copy-constructor is deleted, and only move-constructor is provided - then the compiler will throw an error at you.