среда, ноября 23, 2005

Освобождение памяти, выделенной под vector

Некоторое время назад я писала про выделение памяти под vector и в конце немного затронула тему высвобождения памяти. В большинстве реализаций освободить память, выделенную под vector, можно только с помощью трюка, известного как swap trick. Недавно я вычитала, что в реализации STL из Visual C++ 7.1 память, выделенная под вектор, высвобождается при вызове метода clear(). 7.1 у меня нет, зато у меня есть Microsoft Visual C++ Toolkit 2003. Действительно, освобождается. Для такого кода:

vector <int> v;
v.reserve(17);
cout<<v.capacity()<<endl;
v.clear();
cout<<v.capacity()<<endl;

Вывод получается такой:

Microsoft Visual C++ Toolkit 2003:
17
0 //действительно, освободилась
MSVC++6.0:
17
17
MinGW gcc 3.4.4:
17
17


Я решила копать дальше. Я всегда считала, что метод clear для последовательных контейнеров эквивалентен erase всего контейнера. Нашла упоминание об этом в документации STL на sgi.com. Вот оттуда выдержка:
a.clear() Equivalent to a.erase(a.begin(), a.end())
Запускаю код:
vector <int> v;
v.reserve(17);
cout<<v.capacity()<<endl;
v.erase(v.begin(), v.end());
cout<<v.capacity()<<endl;

Получаю:
Microsoft Visual C++ Toolkit 2003:
17
17 //не освободилась
MSVC++6.0:
17
17
MinGW gcc 3.4.4:
17
17

Получается, что в случае вышеупомянутого тулкита нет обещанной эквивалентности. Так, а что говорит об этом Стандарт? Вот тут интересный момент. Там нет слова "эквивалентно". Там erase(begin(), end()) приписано к clear() в качестве assertion/note для последовательных контейнеров. А вот требование там одно, что post condition: size()==0. Оно тут выполняется.
Так что MSVC++2003 тут прав.
Я пробовала также v.resize(0). Пробовала удалить все элементы вектора с помощью pop_back'ов. Память не освобождается. Это происходит только при вызове clear.

Ссылки по теме:
STL vector and reserve

7 коммент.:

Анонимный комментирует...

For doing this portably, you need to write your own allocator... :-(

Анонимный комментирует...

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

Анонимный комментирует...

size() и capacity() - не есть одно и то же. Память изначально выделяется с некоторым запасом, чтобы при добавлении элементов в вектор не было постоянной переаллокации (ибо это тормоза, чем дальше - тем больше). Так что, capacity() говорит о размере пула памяти, выделеной данному вектору, а вот size() - это кол-во элементов, которые реально используются из этого пула.
Когда capacity() == size(), вставка очередного элемента вызовет
1) аллокацию нового пула (скорее всего, вдвое большего, чем раньше)
2) копирование содержимого старого пула в новый
3) освобождение старого пула
Если Вам не нравится такое поведение - смотрите на другие контейнеры, например, лист (двусвязный список).

Алёна комментирует...

2Анонимный:
size() и capacity() - не есть одно и то же.

Я в курсе :-)

1) аллокацию нового пула (скорее всего, вдвое большего, чем раньше)

Вдвое больший пул использовался в старых компиляторах, в VC++6.0, например. Он ведет к довольно неэффективному использованию памяти. В VC++7 уже используется константа 1.5.

Если Вам не нравится такое поведение

Речь идет об освобождении памяти, выделенной под вектор, а не о выборе контейнеров...

Анонимный комментирует...

Бьерн Страуструп:

Уменьшение размеров вектора не уменьшает его его емкости. Чтобы вернуть память системе, нужно сделать следующий трюк:

vector tmp = v;
v.swap(tmp);

Анонимный комментирует...

Ни .clear(), ни reserve(x) у меня память не освобождают.
Освободить память удаётся только так: std::vector().swap(vec);

Анонимный комментирует...

Просит удаления неиспользуемых мощностей.
Это необязательный просьбой сократить capacity в size. Это зависит от реализации, если запрос выполняется.
void shrink_to_fit(); // (начиная с C++11)

Пример:
std::vector v;
v.clear(); // очищает содержимое вектора .
v.shrink_to_fit(); // сокращает зарезервированный размер памяти вектора .