Rvalue reference arguments allow the efficient transfer of the ownership of objects. Therefore, it is expected that rvalue arguments or their
subobjects are, conditionally or not, moved into their destination variables.
The ownership is unclear when an rvalue argument, including its subobject or elements, is never moved. This might lead to bugs.
This issue can be resolved in multiple ways:
- Generally,
std::move
can be used to move such arguments;
- For containers, C++23
std::views::as_rvalue
can be used to move their elements;
- It is also possible to use a range-based for loop to move elements.
This rule does not apply when the argument is a forwarding reference.
Noncompliant code example
class Shape {
// ...
public:
bool isVisible() const;
};
class DrawingStore {
std::vector<Shape> store;
public:
void insertShape(Shape&& shape) {
if (shape.isVisible()) {
store.emplace_back(shape); // Noncompliant, call to std::move is expected
}
}
void insertAllShapes(std::vector<Shape>&& shapes) {
for (auto& s : shapes) {
if (s.isVisible()) {
store.emplace_back(s); // Noncompliant, call to std::move is expected
}
}
}
};
Compliant solution
class Shape {
// ...
public:
bool isVisible() const;
};
class DrawingStore {
std::vector<Shape> store;
public:
void insertShape(Shape&& shape) {
if (shape.isVisible()) {
store.emplace_back(std::move(shape)); // Compliant
}
}
void insertAllShapes(std::vector<Shape>&& shapes) {
for (auto& s : shapes) {
if (s.isVisible()) {
store.emplace_back(std::move(s)); // Compliant
}
}
}
};
Alternatively, insertAllShapes
could also be rewritten like this using C++23:
void insertAllShapes(std::vector<Shape>&& shapes) {
std::ranges::copy_if(
shapes | std::views::as_rvalue,
std::back_inserter(store),
&Shape::isVisible
);
// Alternatively:
for (auto&& s : shapes | std::views::as_rvalue) {
if (s.isVisible()) {
store.emplace_back(std::move(s)); // Compliant
}
}
}