Reputation: 1223
Please pardon my lack of clarity on this topic. I am trying to create functions for inserting a big class into a vector. In this example, I use vector of ints as the big class.
#include <vector>
#include <iostream>
using namespace std;
vector<vector<int>> vectorOfVectors;
void fn(const vector<int> &v) {
vectorOfVectors.push_back(v);
}
void fn(vector<int> &&v) {
vectorOfVectors.push_back(std::move(v));
}
int main() {
vector<int> a({1});
const vector<int> b({2});
fn(std::move(a));
fn(b);
cout<<b[0];
}
Obviously, I want no copying to be done when possible. My questions:
Upvotes: 5
Views: 1374
Reputation: 33126
Does this code do the right thing?
Yes. C++11 added std::vector::push_back(T&&)
for this very reason.
Is there a better way of doing this?
Your fn(const vector<int> &v)
and fn(vector<int> &&v)
are both doing the same thing, pushing the argument v
onto the end of vectorOfVectors
. In lieu of your two fn
functions you could have used one function template that uses perfect forwarding.
template<typename T>
void fn(T &&v) {
vectorOfVectors.push_back(std::forward<T>(v));
}
How this works is thanks to the C++11 reference collapsing rules and std::forward
. The template type T
becomes vector<int>&
in the case that v
is an lvalue but vector<int>&&
in the case that v
is an rvalue. The reference collapsing rules means that vector<int>& &&
becomes vector<int>&
while vector<int>&& &&
becomes vector<int>&&
. This does exactly what you want, calling the version of push_back
that does a copy in the case of an lvalue but the version that does a move in the case of an rvalue.
One downside is that this sometimes can lead to interesting diagnostics when you get things wrong. ("Interesting" here means hundreds of lines of inscrutable diagnostic text from either g++ or clang++). Another downside is that templates can result in a case of "converters gone wild".
For the same approach to work with custom classes, do I need to define move constructors?
Not necessarily. You'll get an implicitly declared move constructor if the class doesn't declare a user-defined destructor, copy constructor, copy assignment operator, or move assignment operator. The implicitly declared move constructor will be defined as deleted if the class has non-movable data members or derives from a class that can't be moved or deleted.
To me, that's a bit too much to remember. I don't know if this is a good practice or a bad one, but I've started using Foo(const Foo&)=default
, with similar declarations for the other rule of five functions. I'll also qualify the constructors as explicit
in many cases to avoid the "converters gone wild" problem.
Upvotes: 8
Reputation: 88195
a
.Yes. Using push_back
means you're forced to construct at least two objects while emplace_back
and perfect forwarding could do less work.
template<typename... Ts>
auto fn(Ts &&...ts)
-> decltype(vectorOfVectors.emplace_back(std::forward<Ts>(ts)...), void())
{
vectorOfVectors.emplace_back(std::forward<Ts>(ts)...);
}
3. As long as you're using push_back
, you would need the classes to be move constructible in order to avoid copying. You don't necessarily need to define the move contructor yourself though, if you can get the default definition.
Upvotes: 2