Вы правы, что объект x
не будет перемещен из. Повышение производительности операций перемещения связано с другими k
векторами, уже находящимися в V
.
. По мере роста вектора (если reserve
не использовался с достаточным размером), иногда потребуется перераспределить чтобы получить больший кусок памяти, так как его элементы должны быть в непрерывной памяти. Это не случается на каждом push_back
, но в этом примере это иногда случается. Допустим, push_back
и другие функции используют некоторую приватную функцию grow_capacity
, которая получает достаточно памяти и затем создает объекты уже в векторе в этой памяти.
В C ++ 03 единственная Разумным способом создания объектов в новой памяти для произвольного параметра шаблона T
является использование конструктора копирования T
.
// C++03 implementation?
template <typename T, typename Alloc>
std::vector<T, Alloc>::grow_capacity(::std::size_t new_capacity)
{
T* new_data = get_allocator().allocate(new_capacity);
T* new_end = new_data;
try {
for (const_iterator iter = begin(); iter != end(); ++iter) {
::new(static_cast<void*>(new_end)) T(*iter); // T copy ctor!
++new_end;
}
} catch (...) {
while (new_end != new_data) (--new_end)->~T();
get_allocator().deallocate(new_data, new_capacity);
throw;
}
// Clean up old objects and memory.
for (const_reverse_iterator riter = rbegin(); riter != rend(); ++riter)
riter->~T();
get_allocator().deallocate(_data, _capacity);
// Assign private members.
_data = new_data;
_capacity = new_capacity;
}
В C ++ 11 и более поздних версиях, когда std::vector<T>
необходимо перераспределить на большую емкость, ему разрешено перемещать элементы T
вместо их копирования, если это можно сделать без нарушения гарантии строгого исключения. Для этого требуется, чтобы конструктор перемещения был объявлен не генерирующим никаких исключений. Но если конструктор перемещения может сгенерировать, элементы должны быть скопированы старым способом, чтобы вектор оставался в согласованном состоянии, если это произойдет.
// C++17 implementation?
template <typename T, typename Alloc>
std::vector<T, Alloc>::grow_capacity(::std::size_t new_capacity)
{
T* new_data = get_allocator().allocate(new_capacity);
if constexpr (::std::is_nothrow_move_constructible_v<T>) {
::std::uninitialized_move(begin(), end(), new_data); // T move ctor!
} else {
T* new_end = new_data;
try {
for (const T& old_obj : *this) {
::new(static_cast<void*>(new_end)) T(old_obj); // T copy ctor!
++new_end;
}
} catch (...) {
while (new_end != new_data) (--new_end)->~T();
get_allocator().deallocate(new_data, new_capacity);
throw;
}
}
for (const_reverse_iterator riter = rbegin(); riter != rend(); ++riter)
riter->~T();
get_allocator().deallocate(_data, _capacity);
// Assign private members.
_data = new_data;
_capacity = new_capacity;
}
Так в контейнере с типом std::vector<std::vector<int> >
, T
- это std::vector<int>
. Увеличение емкости C ++ 03 иногда требует большого числа конструкторов копирования, а затем деструкторов для std::vector<int>
. Каждый конструктор копирования выделяет некоторую память и копирует 1000 int
значений, а каждый деструктор освобождает часть памяти, так что это действительно сложится. Но в C ++ 11 std::vector
, поскольку тип элемента std::vector<int>
имеет конструктор перемещения noexcept
, контейнер std::vector<std::vector<int>>
может просто использовать этот конструктор перемещения, который представляет собой всего лишь несколько замен скалярных членов, а также заставляет деструкторы перемещенных старых объектов ничего не делать.