Как управляется динамическая память в std::vector?

Как std::vector реализует управление изменяющимся числом элементов: использует ли он функцию realloc() или использует связанный список?

Спасибо.

Ответ 1

Он использует распределитель, который был предоставлен ему в качестве второго параметра шаблона. Так вот. Скажем, что это в push_back, пусть t - объект, который нужно нажать:

...
if(_size == _capacity) { // size is never greater than capacity
    // reallocate
    T * _begin1 = alloc.allocate(_capacity * 2, 0);
    size_type _capacity1 = _capacity * 2;

    // copy construct items (copy over from old location).
    for(size_type i=0; i<_size; i++)
        alloc.construct(_begin1 + i, *(_begin + i));
    alloc.construct(_begin1 + _size, t);

    // destruct old ones. dtors are not allowed to throw here. 
    // if they do, behavior is undefined (17.4.3.6/2)
    for(size_type i=0;i<_size; i++)
        alloc.destroy(_begin + i);
    alloc.deallocate(_begin, _capacity);

    // set new stuff, after everything worked out nicely
    _begin = _begin1;
    _capacity = _capacity1;
} else { // size less than capacity
    // tell the allocator to allocate an object at the right
    // memory place previously allocated
    alloc.construct(_begin + _size, t);
}
_size++; // now, we have one more item in us
...

Что-то вроде этого. Распределитель будет заботиться о распределении памяти. Он сохраняет шаги выделения памяти и построения объекта в эту память отдельно, поэтому он может предварительно распределять память, но еще не вызывает конструкторы. Во время перераспределения вектор должен заботиться об исключениях, которые бросают конструкторы копирования, что несколько усложняет этот вопрос. Выше всего лишь фрагмент кода псевдокода, а не настоящий код и, вероятно, содержит много ошибок. Если размер превышает емкость, он просит распределитель выделить новый больший блок памяти, если не тогда, он просто конструируется в ранее выделенном пространстве.

Точная семантика этого зависит от распределителя. Если это стандартный распределитель, конструкция будет делать

new ((void*)(_start + n)) T(t); // known as "placement new"

И выделение allocate просто получит память из ::operator new. destroy будет вызывать деструктор

(_start + n)->~T();

Все, что абстрагируется за распределителем, и вектор просто использует его. Распределитель стека или объединения может работать совершенно иначе. Некоторые ключевые моменты о vector, которые важны

  • После вызова reserve(N) у вас может быть до N элементов, вставленных в ваш вектор, не рискуя перераспределением. До тех пор это до тех пор, пока size() <= capacity(), ссылки и итераторы к его элементам остаются в силе.
  • Векторное хранилище смежное. Вы можете рассматривать & v [0] как буфер, содержащий столько элементов, которые у вас есть в вашем векторе.

Ответ 2

Одним из жестких правил векторов является то, что данные будут храниться в одном непрерывном блоке памяти.

Таким образом, вы знаете, что теоретически можете это сделать:

const Widget* pWidgetArrayBegin = &(vecWidget[0]);

Затем вы можете передать pWidgetArrayBegin в функции, которые хотят иметь массив в качестве параметра.

Единственным исключением из этого является std::vector <bool> специализация. На самом деле это вовсе не обман, а другая история.

Итак, std::vector перераспределит память и не будет использовать связанный список.

Это означает, что вы можете стрелять в ногу, делая это:

Widget* pInteresting = &(vecWidget.back());
vecWidget.push_back(anotherWidget);

Все, что вы знаете, вызов push_back мог заставить вектор переместить его содержимое в совершенно новый блок памяти, недействительный pInteresting.

Ответ 3

Память, управляемая std::vector гарантирована, будет непрерывной, так что вы можете рассматривать &vec[0] как указатель на начало динамического массива.

Учитывая это, как он на самом деле управляет им, перераспределение является специфичным для реализации.

Ответ 4

std::vector сохраненные данные в смежных блоках памяти.

Предположим, что мы объявляем вектор как

std::vector intvect;

Итак, изначально будет создана память из x элементов. Здесь x зависит от реализации.

Если пользователь вставляет больше чем х элементов, чем новый блок памяти будет создан из 2x (дважды размер) элементов, а исходный вектор будет скопирован в этот блок памяти.

Вот почему всегда рекомендуется резервировать память для вектора, вызывая резерв функция.

intvect.reserve(100);

чтобы избежать удаления и копирования векторных данных.