Глядя на этот список, можно заметить, что пересоздание объекта класса StrVec влечет за собой копирование каждой строки из прежнего объекта StrVec в новый. Даже без подробностей реализации класса string известно, что строки ведут себя подобно значению. После копирования новая строка и оригинальная независимы друг от друга. Изменения, внесенные в оригинал, не распространяются на копию, и наоборот.

Поскольку строки действуют, как значения, можно сделать вывод, что у каждой строки должна быть собственная копия составляющих ее символов. Копирование строки должно резервировать память для этих символов, а удаление строки должно освободить используемую ею память.

Копирование строки подразумевает копирование данных, поскольку обычно после копирования строки у нее будет два пользователя. Но когда функция reallocate() копирует строки объекта класса StrVec, у этих строк будет только один пользователь. Как только копирование элементов из прежнего пространства в новое завершается, исходные строки немедленно удаляются.

Копирование данных этих строк не нужно. Производительность класса StrVec будет значительно выше, если удастся избежать дополнительных затрат на резервирование и освобождение строк при каждом его пересоздании.

Конструктор перемещения и функция std::move()

Копирования строки можно избежать при помощи двух средств, введенных новой библиотекой. Во-первых, некоторые из библиотечных классов, включая класс string, определяют так называемые конструкторы перемещения (move constructor). Подробности работы конструктора перемещения класса string (равно как и все остальные подробности его реализации) не раскрываются. Однако общеизвестно, что конструкторы перемещения обычно "перемещают" ресурсы из заданного объекта в создаваемый. Библиотека гарантирует также то, что "перемещенная" строка останется в допустимом состоянии. В случае класса string можно предположить, что у каждого его объекта есть указатель на массив типа char. По-видимому, конструктор перемещения класса string копирует указатель вместо резервирования нового пространства и копирования символов.

Второе доступное для использования средство — это библиотечная функция move(), определенная в заголовке utility. Есть два важных момента, которые следует знать о функции move(). Во-первых, по причинам, рассматриваемым в разделе 13.6.1, когда функция reallocate() создает строки в новой области памяти, она должна вызвать функцию move(), чтобы сообщить о необходимости использования конструктора перемещения класса string. Если пропустить вызов функции move(), то будет использован конструктор копий класса string. Во-вторых, по причинам, рассматриваемым в разделе 18.2.3, объявление using (см. раздел 3.1) для функции move() обычно не предоставляется. Когда используется функция move(), вызывается функция std::move(), а не move().

Функция-член reallocate()

Используя эту информацию, можно написать собственную функцию reallocate(). Сначала вызовем функцию allocate(), чтобы зарезервировать новое пространство. При каждом пересоздании объекта класса StrVec будем удваивать его емкость. Если вектор StrVec пуст, резервируем место для одного элемента:

void StrVec::reallocate() {

 // будем резервировать вдвое больше элементов, чем текущий размер

 auto newcapacity = size() ? 2 * size() : 1;

 // резервировать новую память

 auto newdata = alloc.allocate(newcapacity);

 // переместить данные из прежней памяти в новую

 auto dest = newdata; // указывает на следующую свободную позицию в

                      // новом массиве

 auto elem = elements; // указывает на следующий элемент в старом

                       // массиве

 for (size_t i = 0; i != size(); ++i)

  alloc.construct(dest++, std::move(*elem++));

 free(); // освобождает старое пространство после перемещения

         // элементов

 // обновить структуру данных, чтобы указать на новые элементы

 elements = newdata;

 first_free = dest;

 cap = elements + newcapacity;

}

Перейти на страницу:

Похожие книги