Reputation: 1811
I have a std::vector
of objects of a certain class A
. The class is non-trivial and has copy constructors and move constructors defined.
std::vector<A> myvec;
If I fill-up the vector with A
objects (using e.g. myvec.push_back(a)
), the vector will grow in size, using the copy constructor A( const A&)
to instantiate new copies of the elements in the vector.
Can I somehow enforce that the move constructor of class A
is beging used instead?
Upvotes: 113
Views: 23860
Reputation: 27088
You need to inform C++ (specifically std::vector
) that your move constructor and destructor does not throw, using noexcept
. Then the move constructor will be called when the vector grows.
This is how to declare and implement a move constuctor that is respected by std::vector
:
A(A && rhs) noexcept {
std::cout << "i am the move constr" <<std::endl;
... some code doing the move ...
m_value=std::move(rhs.m_value) ; // etc...
}
If the constructor is not noexcept
, std::vector
can't use it, since then it can't ensure the exception guarantees demanded by the standard.
For more about what's said in the standard, read C++ Move semantics and Exceptions
Credit to Bo who hinted that it may have to do with exceptions. Also consider Kerrek SB's advice and use emplace_back
when possible. It can be faster (but often is not), it can be clearer and more compact, but there are also some pitfalls (especially with non-explicit constructors).
Edit, often the default is what you want: move everything that can be moved, copy the rest. To explicitly ask for that, write
A(A && rhs) = default;
Doing that, you will get noexcept when possible: Is the default Move constructor defined as noexcept?
Note that early versions of Visual Studio 2015 and older did not support that, even though it supports move semantics.
Upvotes: 157
Reputation: 2711
Interestingly, gcc 4.7.2's vector only uses move constructor if both the move constructor and the destructor are noexcept
. A simple example:
struct foo {
foo() {}
foo( const foo & ) noexcept { std::cout << "copy\n"; }
foo( foo && ) noexcept { std::cout << "move\n"; }
~foo() noexcept {}
};
int main() {
std::vector< foo > v;
for ( int i = 0; i < 3; ++i ) v.emplace_back();
}
This outputs the expected:
move
move
move
However, when I remove noexcept
from ~foo()
, the result is different:
copy
copy
copy
I guess this also answers this question.
Upvotes: 19
Reputation: 5265
It seems, that the only way (for C++17 and early), to enforce std::vector
use move semantics on reallocation is deleting copy constructor :) . In this way it will use your move constructors or die trying, at compile time :).
There are many rules where std::vector
MUST NOT use move constructor on reallocation, but nothing about where it MUST USE it.
template<class T>
class move_only : public T{
public:
move_only(){}
move_only(const move_only&) = delete;
move_only(move_only&&) noexcept {};
~move_only() noexcept {};
using T::T;
};
or
template<class T>
struct move_only{
T value;
template<class Arg, class ...Args, typename = std::enable_if_t<
!std::is_same_v<move_only<T>&&, Arg >
&& !std::is_same_v<const move_only<T>&, Arg >
>>
move_only(Arg&& arg, Args&&... args)
:value(std::forward<Arg>(arg), std::forward<Args>(args)...)
{}
move_only(){}
move_only(const move_only&) = delete;
move_only(move_only&& other) noexcept : value(std::move(other.value)) {};
~move_only() noexcept {};
};
Your T
class must have noexcept
move constructor/assigment operator and noexcept
destructor. Otherwise you'll get compilation error.
std::vector<move_only<MyClass>> vec;
Upvotes: -1