2

I'm reading the documentation of vector. Here I found that:

~vector(); calls allocator_traits::destroy on each of the contained elements, and deallocates all the storage capacity allocated by the vector using its allocator.

I don't understand how can you delete an element without deallocating it.

Going further with destroy (allocator_type& alloc, T* p); :

Destroys the element object pointed by p without deallocating its storage. In the non-specialized definition of allocator_traits, this member function calls alloc.destroy(p) if such a call is well formed. Otherwise, it invokes p->~T().

  • I don't understand what they mean by destroy the element without deallocating its storage (again).
  • How would this involve p->~T() if my vector<T> obj {T(), T()} consists of automatically objects, not pointers?
  • How could it call destroy on a T** if my vector is vector<T*>...?

I'm trying to do a parallel between calling the destructor on objects like:

  1. MyClass obj() respectively
  2. MyClass obj = new MyClass()

vs

  1. vector<T> obj {T(), T()}
  2. vector<T*> obj {new T(), new T()}

and I can't see how they are similar, because they should be as one contains the other.

Cătălina Sîrbu
  • 1,253
  • 9
  • 30
  • *"how can you delete an element without deallocating it."* -- you cannot, as "`delete`" means "destroy and deallocate". That is why the documentation you quote does not use the word "delete". – JaMiT Apr 16 '20 at 16:01
  • 2
    Related, but its perspective might preclude it from being a true duplicate: [Destroy vs Deallocate](https://stackoverflow.com/questions/18077898/destroy-vs-deallocate) – JaMiT Apr 16 '20 at 16:03
  • 2
    See also [As the delete operator deallocates memory, why do I need a destructor?](https://stackoverflow.com/questions/9125271/as-the-delete-operator-deallocates-memory-why-do-i-need-a-destructor) for more on "delete" vs. "deallocate" – JaMiT Apr 16 '20 at 16:08
  • 1
    I strongly recommend never using `new` (I've not needed it at all for almost 9 years now), and instead use `std::vector` or `std::unique_ptr` (and it's factory function `std::make_unique`) or for polymophism in a vector combine both for `std::vector>`. – Eljay Apr 16 '20 at 16:55
  • 1
    See Also: [How to properly free the memory allocated by placement new?](https://stackoverflow.com/questions/8918791/how-to-properly-free-the-memory-allocated-by-placement-new/845092) – Mooing Duck Apr 16 '20 at 18:49

3 Answers3

2

A vector<T> holds a chunk of memory, and that memory has some number of T objects in them. The vector manages the storage separately from the objects.

When you call v.pop_back(), it destroys the last element in the vector, but does not release the storage occupied by that element. If you then call v.push_back(), it will place the new (last) element in the location previously occupied by the element that you erased.

Marshall Clow
  • 15,972
  • 2
  • 29
  • 45
  • So for example if i were to knew the `v[last_index]` address before `v.pop_back()`, after running this `pop() method` , using v[last_index]` the last value would be still accessible? – Cătălina Sîrbu Apr 16 '20 at 16:58
  • 1
    The storage would still be there, yes, but the 'value' (the object) no longer exists. It has been destroyed. – Marshall Clow Apr 16 '20 at 17:47
  • 1
    @CătălinaSîrbu Using `v[last_index]` in that situation is much like [accessing a local variable outside its function](https://stackoverflow.com/a/6445794) -- if it works, it's because the compiler is not actively trying to thwart you. – JaMiT Apr 16 '20 at 19:07
1

I don't understand how can you delete an element without deallocating it.

As the next quotation mentions, this is ultimately done through calling its destructor manually. The important part is to do so correctly, as delete will already call it for you.

Minus some details and layers of abstraction, the way vector works is through placement new, which can construct an object in existing storage, separating the memory allocation from the construction. This allows vector to manage its storage separately when resizing as well as to avoid the need for a default constructor (or any other) when there is extra memory with no object there:

char memory[2 * sizeof(T)]; // No Ts yet. Keep in mind alignment is important too.
new(memory) T{/* constructor arguments */}; // One T at the start of the memory, rest unused. 
new(memory + sizeof(T)) T{}; // Two Ts next to each other in the memory.
static_cast<T*>(memory)->~T(); // First part of memory now unused again.
// Destroy any other objects appropriately.
// Free store memory would be explicitly deallocated here.

How would this [invoke] p->~T() if my vector obj {T(), T()} consists of automatically objects, not pointers?

The vector provides this function a pointer to that value. The vector knows where in memory the element is and knows there's a T object there, so it can obtain a proper pointer. destroy takes the pointer and falls back to calling the destructor through that pointer. This is safe because the object was constructed in an appropriate manor instead of through new T(…).

How could it call destroy on a T** if my vector is vector...?

It works the same way when the element type is a pointer. The vector obtains a pointer to that element (so a pointer to a pointer in this case) and passes it to the destroy function. The destroy function calls the destructor through that pointer. Now you might be wondering why that works when pointers don't have destructors, but there's a special rule saying that a destructor call on a fundamental type is valid through an aliased type (the name T in this case) and does nothing. This keeps generic code working without different code for different Ts.

chris
  • 60,560
  • 13
  • 143
  • 205
  • Calling a destructor for an address ( a pointer ) it's equivalent to calling `delete x` ? _where `x` is an int ( let's assume `int` and pointer have the same underlying data type - no that it matters for this example)_ – Cătălina Sîrbu Apr 16 '20 at 17:10
  • 1
    @CătălinaSîrbu, I'm not quite sure what you're asking. Calling a destructor _through_ a pointer will still destruct the object being pointed to. Calling a destructor on the pointer itself (through the special rule) will do nothing. Using `delete` will do both of the destruction and the memory cleanup parts (and is only valid if you use it on something obtained from a regular `new` call). Destruction means actually getting rid of the object and memory cleanup means getting rid of the memory used to hold that object. In `vector`'s case, doing both would mean the vector is always at capacity. – chris Apr 16 '20 at 17:21
  • Also, thank you very much for the example provided, but I'm not sure I understand the quoted code. I'm kind of beginner and I didn't get the ideea. – Cătălina Sîrbu Apr 16 '20 at 17:21
  • Honestly, how `std::vector` works isn't a great starting point for beginners. The language has the machinery to make it work, but it's complicated machinery even for most people who use the language regularly. A naive vector that deals only with size and not capacity can be a good way to learn how manual memory management works should the need arise to know that (or having default values in unused slots). However, manual memory management tends to be needed for working with old code rather than for writing new code. – chris Apr 16 '20 at 17:26
  • I come back after some supplementary readings, so I didn;t know I can actually allocate memory on my own with that `char` stuff. JaMiT helped me with that providing a link – Cătălina Sîrbu Apr 16 '20 at 19:47
1

I'd like to give another view here and focus on the vector of pointers

vector<T*> obj {new T(), new T()}

and the difference between destroying and deallocating the pointers in it. (That's how I interpret your question, but maybe you know all of this already :D)

First of: this vector contains only pointers. A pointer is usually 4 or 8 bytes long. Let's assume it's 8 and the vector contains 2 pointers, so it has to allocate 16 bytes, regardless of sizeof(T). The two objects allocated with new are somewhere else on the heap.

Let's assume this vector get's destroyed, for example because it goes out of scope in some function. It will destroy every element inside the vector and deallocate the memory that was allocated by the vector (e.g. 16 bytes in this example).

  • Destroying a pointer, doesn't do anything, since a pointer is not much more than a numeric value that specifies a location in memory. Though a T might have a destructor, a T* doesn't have one.
  • Deallocating means only to release the memory that is used to store the pointers. The memory those pointers point to is completely untouched.

It's similar to the other example you gave:

MyClass *obj = new MyClass();

If obj goes out of scope the destructor of MyClass will not be called, obj itself (4 or 8 bytes as above) will be deallocated, but the memory obj pointed to won't be touched. This results in a memory leak and the same happens for the vector above.

Only the pointers are gone, the memory they pointed to remains.


It's almost the same for a vector with values

vector<T> obj {T(), T()}

Same scenario as above, but now the objects are stored inside the memory allocated by the vector. Thus the vector must now allocate 2 * sizeof(T) bytes to contain the two objects. When the vector gets destroyed the same as above happens:

  • Each element gets destroyed. If T has a destructor it is called.
  • The memory allocated by the vector (the 2 * sizeof(T) bytes) is deallocated

It's similar to

MyClass obj;

When this goes out of scope the destructor ~MyClass will be called and the memory of obj (sizeof(MyClass) bytes) will be deallocated. No memory leak happens, just like in the vector above.

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
Lukas-T
  • 11,133
  • 3
  • 20
  • 30
  • _It will destroy every element and deallocate the memory it has allocated._ here you are referring to the memory that `vector` stores (like the 16 bytes)? Also, `destroying` is always followed by `deallocating` ? This is meant by `delete`? – Cătălina Sîrbu Apr 16 '20 at 17:17
  • Yes, I was refering to the memory owned by the vector, will clarify. And yes, in this the objects will be destroyed, then the memory will be deallocated. But it's not always the case as Marhsall Clow's answer details. – Lukas-T Apr 16 '20 at 18:30