Как принудительно применять семантику перемещения при росте вектора?
У меня есть 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;