0

I wrote a program a few months ago using Vectors. I used the clear() member function to "reset" the vectors, assuming that it would not only clear the items in the elements out and reset the size data member, but that it would also give the heap back the memory that was being used with it previously. Well, I stumbled onto a post about vectors saying that this is not the correct way to get memory back from the Vector, as using clear() will not do it, but that one needed to use the swap method:

vector<MyClass>().swap(myVector);

I'm curious as to why we have to call the swap to delete the old memory? I assume this is more of a workaround, in that we are using the swap, but something else is happening. Is a destructor being called at all?

One last question, all of the articles that I've now read saying that clear() doesn't deallocate memory that that the objects are "destroyed." Can anyone clarify what is meant by that? I'm unfamiliar with the vernacular. I assumed that if an object was destroyed, it was cleared out and the memory was given back to the heap, but this is wrong, so is the word "destroy" referring to just wiping the bits associated with each element? I'm not sure. Any help would be greatly appreciated. Thanks.

JosephTLyons
  • 2,075
  • 16
  • 39
  • 3
    std::vector::clear() should trigger the destructors for the elements in the vector. For a sanity check, you can put a print statement in your MyClass object to see it happening. – davepmiller Aug 23 '16 at 00:27
  • 1
    You most likely misread whatever you read. – Sam Varshavchik Aug 23 '16 at 00:44
  • 1
    Vectors keep their capacity when you clear them because they assume that you're going to add new elements to the vector. So it's more efficient to keep the memory around rather than reallocate it when you add to it. – Barmar Aug 23 '16 at 00:45
  • Normally when you're done with a vector you leave the scope where it was declared, and then it will be destroyed completely. If you really need to reset it, the `swap` method is available. – Barmar Aug 23 '16 at 00:46
  • You need to distinguish the memory used by the vector itself, and the memory used by the `MyClass` objects in the vector. When you clear the vector, the latter objects are destroyed and their memory reclaimed, but not the vector itself (which is internally just an array of pointers). – Barmar Aug 23 '16 at 00:48
  • @Barmar Most vector implementations I know of allocate an array of size `sizeof(T)*capacity()` in which the members are stored directly in the array - not an array of pointers. – Daniel Schepler Aug 23 '16 at 00:53
  • @DanielSchepler It's a few years old, but http://stackoverflow.com/a/8036528/1491895 says that vector elements are allocated in the heap. – Barmar Aug 23 '16 at 00:55
  • Yes, the array storage is allocated on the heap - but it's laid out like a `T[]` array (except that not all elements are initialized) not a `T*[]`. – Daniel Schepler Aug 23 '16 at 00:58
  • Oh, I see. The internal array of the vector is in the heap, so that it can be resized. – Barmar Aug 23 '16 at 00:58
  • @DanielSchepler that layout is actually compulsory, because the vector contents must be able to behave like a C-style array – M.M Aug 23 '16 at 01:07

2 Answers2

3

To answer the question, you need to separate the memory directly allocated by the vector from memory indirectly "owned" through the member objects. So for example, say MyClass is an object taking 1000 bytes, and then you work with a std::vector<std::unique_ptr<MyClass>>. Then if that vector has 10 elements, the directly allocated memory will typically be close to 10*8=80 bytes on a 64-bit system, whereas the unique_ptr objects indirectly own 10*1000=10000 bytes.

Now, if you call clear() on the vector, the destructor is called on each unique_ptr element, so the 10000 indirectly-owned bytes are freed. However, the underlying array storage isn't freed, so the 80+ bytes directly owned by the vector are still allocated. (At this point, the vector has a capacity() of at least 10, but a size() of 0.) If you subsequently call the vector's destructor, that will cause the storage array to be freed as well.

Now, if you execute

std::vector<std::unique_ptr<MyClass>>().swap(v);

let's break down what that does: first, a temporary vector object is created, which has no allocated array and a capacity() of 0. Now, the swap transfers the underlying array of v to the temporary vector, and swaps the null or empty array from the temporary vector into v. Finally, at the end of the expression, the temporary object goes out of scope so its destructor is called. That causes the destructors of any elements previously belonging to v to be called, followed by freeing the underlying array storage that previously belonged to v. So at the end of this, v.capacity() is 0, and all memory previously belonging to v is freed, whether it was directly allocated by v or indirectly belonged to it through the stored unique_ptr objects.

Daniel Schepler
  • 3,043
  • 14
  • 20
  • @DanielSchepler, Your answer is very specific and very appreciated. I just have one question. So calling the swap function will effectively free the same memory that clear() frees (destroying the objects) and frees the memory associated with vector itself, so if a user were to do a clear(), then a swap, it would be redundant as the swap itself frees both sets of memories. Is this correct? I'm just trying to make sure I understand correctly. It seems like swap is covering both bases when freeing memory, and clear only covers one. – JosephTLyons Aug 23 '16 at 05:06
2

A vector has an associated quantity called capacity which means that it has allocated enough memory for that many elements, even if it does not actually contain that many elements at the moment.

If elements are added or removed from the vector without exceeding the capacity, then no memory is allocated or freed; the individial elements' constructors and destructors are run on the space that's already allocated.

The clear() function doesn't change the capacity. However, the usual implementation of vector::swap() also swaps the capacities of the vectors; so swapping with an empty vector will cause the original vector to have the default capacity, which will be small or even zero (implementation-dependent) and therefore memory should be released.

Since C++11 there is a formal way to reduce capacity, called shrink_to_fit().

Note that the C++ Standard does not actually require that memory be released to the OS after reducing the capacity; it would be up to a combination of the author of the library implementation you use, and the operating system.

M.M
  • 138,810
  • 21
  • 208
  • 365