Как вектор не разрушает элемент дважды после уменьшения его размера? - PullRequest
2 голосов
/ 05 июля 2019

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

class A
{
    A()
    { std::cout << "A constructed") << std::endl; }
    ~A()
    { std::cout << "A destroyed") << std::endl; }
}

main()
{
    std::vector<A> vec(3, A());
    vec.resize(2);
    std::cout << "vector resized" << std::endl;
}

Вывод

A constructed       (1)
A constructed       (2)
A constructed       (3)
A destroyed         (1)
Vector resized
A destroyed         (2)
A destroyed         (3)

Когда вызывается vec.resize(2), третий элемент уничтожается, но емкость вектора остается равной 3. Затем, когда vec уничтожается, все его элементы, включая уже уничтоженный, должны быть уничтожены.Как std::vector узнает, что он уже уничтожил этот элемент?Как я могу реализовать это в моем векторном классе?

Ответы [ 3 ]

2 голосов
/ 05 июля 2019

Есть разница между емкостью и размером. С учетом std::vector<T> v; Вектор выделил память для v.capacity() элементов. Но только в первых v.size() местах содержатся построенные T объекты.

Итак, v.reserve(1000) для пустого вектора не вызовет никаких дополнительных конструкторов. vec.resize(2) в вашем примере уничтожает последний элемент, а vec[2] теперь пустое место в памяти, но память все еще принадлежит vec.

Я думаю, что ваше распределение выглядит как buffer = new T[newSize];. Это не то, как работает std::vector, что не позволяет Ts, которые не имеют конструкторов по умолчанию. Возможно, вы этого не понимали, но всякий раз, когда вы получаете кусок памяти, он уже содержит объекты, пусть это будет T x; или даже new double[newSize];, который возвращает массив значений типа double (хотя их конструкторы пусты).

Существует только один способ получить пригодную для использования неинициализированную память в C ++, а именно выделить chars. Это связано с строгим правилом алиасинга. Также существует (должен | быть) способ явного вызова конструктора в этой памяти, т. Е. Как создать там объект. Вектор использует то, что называется размещение новых , что делает именно это. Распределение тогда просто buffer = new char[newSize*sizeof(T)];, которое не создает никаких объектов.

Время жизни объектов управляется этим новым оператором размещения и явными вызовами деструкторов. emplace_back(arg1,arg2) может быть реализовано как {new(buffer + size) T(arg1,arg2);++size;}. Обратите внимание, что просто делать buffer[size]=T(arg1,arg2); неправильно и UB. operator= ожидает, что левый размер (*this) уже существует.

Если вы хотите уничтожить объект, например, с помощью pop_back, вы должны сделать buffer[size].~T();--size;. Это одно из немногих мест, где вы должны явно вызывать деструктор.

1 голос
/ 06 июля 2019

Когда вызывается vec.resize(2), третий элемент уничтожается, но емкость вектора остается равной 3.

Да.capacity - это количество элементов, которые физически может содержать внутренний массив вектора.size - сколько элементов в этом массиве действительно допустимо.Сжатие size никак не влияет на capacity.

Затем, когда vec уничтожено, все его элементы, включая уже уничтоженный, должны быть уничтожены.

Третий элемент, который был ранее уничтожен и удален из массива, больше не уничтожается.Только size количество элементов уничтожено, а не capacity количество элементов, как вы думаете.

Как std::vector узнает, что он уже уничтожил этот элемент?

Отслеживает size и capacity отдельно.Когда элемент удаляется из массива, элементы, следующие за ним, перемещаются вниз по массиву на 1 слот каждый, и значение size уменьшается.

1 голос
/ 05 июля 2019

Простой ответ заключается в том, что vector внутренне управляет вызовами конструктора и деструктора, часто с помощью методов оператора inplace new и оператора delete.

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

...