Reputation: 622
I have a method my func
that gets a vector from get_vec
and passes it to some constructor of class A
.
class A
{
public:
A(std::vector<int>&& vec) : m_vec(std::move(vec)) {}
std::vector<int> m_vec;
};
std::vector<int> get_vec()
{
std::vector<int> res;
// do something
return res;
}
void my_func()
{
std::vector<int> vec = get_vec();
A(std::move(vec));
}
Ideally I would like the vector to be constructed once, but in this example I create the vector in get_vec
, then copied it to my_func
, moved it to a constructor and then again moved it to A::m_vec
.
What is the correct and efficient way to pass the vector?
Upvotes: 2
Views: 399
Reputation: 25388
I decided to rewrite this answer as Jorge Perez kindly pointed out that the original was flawed, and because none of the other answers really address the original question in its entirety.
I started by writing a simple test program:
#include <iostream>
class Moveable
{
public:
Moveable () { std::cout << "Constructor\n"; }
~Moveable () { std::cout << "Destructor\n"; }
Moveable (const Moveable&) { std::cout << "Copy constructor\n"; }
Moveable& operator= (const Moveable&) { std::cout << "Copy assignment\n"; return *this; }
Moveable (const Moveable&&) { std::cout << "Move constructor\n"; }
Moveable& operator= (const Moveable&&) { std::cout << "Move assignment\n"; return *this; }
};
class A
{
public:
A (Moveable &&m) : m_m (std::move (m)) {}
Moveable m_m;
};
Moveable get_m ()
{
Moveable res;
return res;
}
int main ()
{
Moveable m = get_m ();
A (std::move (m));
}
which gives the following output:
Constructor
Move constructor
Destructor
Destructor
So you can immediately see that the inefficiencies in your code are not as bad as you think - there's just one move and no copies.
Now, as others have said:
Moveable m = get_m ();
doesn't copy anything because of Named Return Value Optimisation (NRVO).
And there's only one move because:
A (std::move (m));
doesn't actually move anything (it's just a cast).
With regard to the move, it's clearer what's going on, arguably, if you change this:
A (Moveable&& m) : m_m (std::move (m)) {}
to this:
A (Moveable& m) : m_m (std::move (m)) {}
because you can then change this:
A (std::move (m));
to this:
A {m};
and still get the same output (you need the braces to avoid 'the most vexing parse').
Upvotes: 0
Reputation: 23681
Compiler optimizations are usually only allowed if they don't change observable behavior. However, copy elision is one of the few cases where even changing the program's output (compared to that of the abstract machine) is allowed in the name of performance.
We can demonstrate this with a mock vector whose special member functions have side effects (such as writing to a volatile
variable):
class MyVec
{
public:
MyVec() { x = 11; };
MyVec(const MyVec&) { x = 22; }
MyVec(MyVec&&) { x = 33; }
MyVec& operator=(const MyVec&) { x = 44; return *this; }
MyVec& operator=(MyVec&&) { x = 55; return *this; }
// Make this the same size as a std::vector
void* a = nullptr;
void* b = nullptr;
void* c = nullptr;
};
If we inspect the optimized assembly, we can see that only one default constructor and one move constructor's side effects are actually kept in my_func
, everything else is optimized away. The first default constructor is the inlined get_vec
, the other is the move in the constructor of A
. That's as efficient as you could possibly be when constructing a member from a temporary.
This is allowed because copies can be elided when return
ing from a function, as well as when initializing from (more or less) a temporary. The latter used to be "you can elide the copy in X x = getX();
", but since C++ 17 versions no temporary is ever created (https://en.cppreference.com/w/cpp/language/copy_initialization).
Upvotes: 1
Reputation: 136237
std::vector<int>&& m_vec
member will cause a dangling reference if you do A a(get_vec());
, for example.
The correct and safe way is to have that member by value:
std::vector<int> m_vec;
Upvotes: 3