Как принудительно применять семантику перемещения при росте вектора?

У меня есть std::vector объектов определенного класса A. Класс является нетривиальным и имеет конструкторы копирования и определенные конструкторы перемещения.

std::vector<A>  myvec;

Если я заполняю вектор объектами A (используя, например, myvec.push_back(a)), вектор будет расти в размере, используя конструктор копирования A( const A&) для создания новых копий элементов в векторе.

Можно ли каким-то образом заставить, чтобы вместо этого использовался конструктор перемещения класса A?

Ответ 1

Вам нужно сообщить С++ (в частности std::vector), что ваш конструктор перемещения и деструктор не выбрасывают, используя noexcept. Тогда конструктор перемещения будет вызываться, когда вектор растет.

Вот как объявить и реализовать конструктор перемещения, который соблюдается 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...
}

Если конструктор не noexcept, std::vector не может его использовать, так как тогда он не может гарантировать гарантии исключения, требуемые стандартом.

Подробнее о том, что сказано в стандарте, читайте Семантика и исключения С++ Move

Кредит Бо, который намекнул, что это может иметь отношение к исключениям. Также рассмотрите советы Kerrek SB и используйте emplace_back, когда это возможно. Это может быть быстрее (но часто это не так), оно может быть более четким и компактным, но есть и некоторые подводные камни (особенно с неявными конструкторами).

Изменить, часто по умолчанию вы хотите: переместите все, что можно переместить, скопируйте остальные. Чтобы явно спросить об этом, напишите

A(A && rhs) = default;

Выполняя это, вы получите noexcept, когда это возможно: Определяется ли конструктор Move по умолчанию как noexcept?

Обратите внимание, что ранние версии Visual Studio 2015 и старше не поддерживали это, хотя он поддерживает семантику перемещения.

Ответ 2

Интересно, что вектор gcc 4.7.2 использует только конструктор перемещения, если и конструктор перемещения, и деструктор noexcept. Простой пример:

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

Это выводит ожидаемое:

move
move
move

Однако, когда я удаляю noexcept из ~foo(), результат отличается:

copy
copy
copy

Думаю, это также отвечает на этот вопрос.

Ответ 3

Кажется, что единственный способ (для С++ 17 и раннего), чтобы принудительно использовать std::vector использовать семантику перемещения при перераспределении, заключается в удалении конструктора копии:). Таким образом, он будет использовать ваши конструкторы перемещения или умереть во время компиляции:).

Существует множество правил, в которых std::vector НЕ ДОЛЖЕН использовать конструктор перемещения при перераспределении, но ничего о том, где он ДОЛЖЕН ИСПОЛЬЗОВАТЬ.

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

или

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

Живой код

В вашем классе T должен быть noexcept перемещать оператор конструктора/ассистента и noexcept деструктор. В противном случае вы получите ошибку компиляции.

std::vector<move_only<MyClass>> vec;