当向量增长时如何强制执行移动语义?

我有一个 std::vector对象的某个类 A。这个类是非平凡的,并且定义了复制构造函数 还有 move 构造函数。

std::vector<A>  myvec;

如果我用 A对象填充向量(例如使用 myvec.push_back(a)) ,向量的大小将会增长,使用复制建构子 A( const A&)来实例化向量中元素的新副本。

我能以某种方式强制使用类 A的 move 构造函数吗?

20970 次浏览

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.

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.

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;
};

Live

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 {};
};

Live code

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;