Cătălina Sîrbu
Cătălina Sîrbu

Reputation: 1283

delete and deallocate vs delete without deallocate

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'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.

Upvotes: 2

Views: 1678

Answers (3)

Lukas-T
Lukas-T

Reputation: 11340

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.

Upvotes: 1

Qaz
Qaz

Reputation: 61900

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.

Upvotes: 1

Marshall Clow
Marshall Clow

Reputation: 16670

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.

Upvotes: 2

Related Questions