Your first feeling is good. Strictly speaking it is not optimal. But it is so close to optimal that you would be justified in saying you don't care.
Explanation:
Foo(std::vector<SomeType> data) noexcept : data_(std::move(data)) {};
When the client passes in an lvalue std::vector<SomeType>
1 copy will be made to bind to the data
argument. And then 1 move will be made to "copy" the argument into data_
.
When the client passes in an xvalue std::vector<SomeType>
1 move will be made to bind to the data
argument. And then another move will be made to "copy" the argument into data_
.
When the client passes in a prvalue std::vector<SomeType>
the move will be elided in binding to the data
argument. And then 1 move will be made to "copy" the argument into data_
.
Summary:
client argument number of copies number of moves
lvalue 1 1
xvalue 0 2
prvalue 0 1
If you instead did:
Foo(const std::vector<SomeType>& data) : data_(data) {};
Foo( std::vector<SomeType>&& data) noexcept : data_(std::move(data)) {};
Then you have a very slightly higher performance:
When the client passes in an lvalue std::vector<SomeType>
1 copy will be made to copy the argument into data_
.
When the client passes in an xvalue std::vector<SomeType>
1 move will be made to "copy" the argument into data_
.
When the client passes in a prvalue std::vector<SomeType>
1 move will be made to "copy" the argument into data_
.
Summary:
client argument number of copies number of moves
lvalue 1 0
xvalue 0 1
prvalue 0 1
Conclusion:
std::vector
move constructions are very cheap, especially measured with respect to copies.
The first solution will cost you an extra move when the client passes in an lvalue. This is likely to be in the noise level, compared to the cost of the copy which must allocate memory.
The first solution will cost you an extra move when the client passes in an xvalue. This could be a weakness in the solution, as it doubles the cost. Performance testing is the only reliable way to assure that either this is, or is not an issue.
Both solutions are equivalent when the client passes a prvalue.
As the number of parameters in the constructor increases, the maintenance cost of the second solution increases exponentially. That is you need every combination of const lvalue and rvalue for each parameters. This is very manageable at 1 parameter (two constructors), less so at 2 parameters (4 constructors), and rapidly becomes unmanageable after that (8 constructors with 3 parameters). So optimal performance is not the only concern here.
If one has many parameters, and is concerned about the cost of an extra move construction for lvalue and xvalue arguments, there are other solutions, but they involve relatively ugly template meta-programming techniques which many consider too ugly to use (I don't, but I'm trying to be unbiased).
For std::vector
, the cost of an extra move construction is typically small enough you won't be able to measure it in overall application performance.
noexcept
either because thevector
move constructor is not markednoexcept
in the standard. However as a practical matter, I'm aware of only one vendor that does not mark the vector move constructor withnoexcept
as an extension. – Sirdar