C++: how does C++ know to destruct smart pointers inside containers?

Viewed 107

In The C++ Programming Language, there is the following example (section 3.2.4).

unique_ptr<Shape> read_shape(istream& is);

void user()
{
    vector<unique_ptr<Shape>> v;
    while (cin)
        v.push_back(read_shape(cin));
    draw_all(v);
    // call draw() for each element
    rotate_all(v,45);
    // call rotate(45) for each element
} // all Shapes implicitly destroyed

Stroustrup is trying to make the point that by using unique_ptrs, one doesn't have to manually loop through the vector and delete all Shapes. I'm having trouble understanding why this is true.

The way I understand smart pointers is that if a smart pointer sptr is allocated on the stack, with something like

std::unique_ptr<Shape> sptr(new Shape());

you don't need to call delete on it at the end of the function. However, from what I can tell in the above example those smart pointers are not on the stack, they're put inside the data array of v, which is on the heap.

5 Answers

Whether the unique_ptr is on the stack or the heap doesn't really matter. What matters is that it will automatically call delete on the target pointer when its destructor runs.

When the vector goes out of scope, the vector's destructor runs. That destroys all contained elements (calling their destructors in the process) and then deallocates its heap buffer.

This is what destructors are for. You don't see the "manual loop" that destroys the unique pointers because that loop is within the destructor of the vector. All containers destroy their elements.

When the destructor for std::vector runs it calls the destructor for each object it contains. The destructor for std::unique_ptr calls delete/delete[] on the pointer it wraps, so all of the memory that was allocated will be cleaned up correctly.

Yes, if you create something on the heap, you have to delete it yourself. Unlike automatic variables (those on the stack, even though the standard doesn't actually mandate the existence of a stack), the lifetime (scope) of those objects can continue across function return boundaries.

However, the vector here is not on the heap - it's actually an automatic variable that will undoubtedly store some of its information on the heap (the array holding the vector elements, most likely), but the variable itself is not.

That means, when it goes out of scope on exit from user(), the destructor is called for it. That destructor is smart enough to free the heap-specific stuff for the vector itself, and call the destructor for each element in the vector.

And, of course, destructing each of those elements may cause further destructors to be called. If one of those elements being destructed is a smart pointer, it will know that it needs to release its hold on the underlying pointer, by either freeing it or decrementing a count to say it has no further interest in it.

When a std::unique_ptr itself is destructed or reset, it calls delete or delete[] (or a custom deleter that you specify) on the object/array that it is currently holding a pointer to.

If the std::unique_ptr is created in automatic memory, such as the stack, it is destructed when it goes out of scope.

If the std::unique_ptr is created in dynamic memory, such as the heap, it is destructed when delete is called on it.

A container owns the elements that it stores. It knows whether its elements are of a type that defines a destructor or not, and if so then it will call that destructor on each element when needed, such as when removing individual elements from the container, or when the container itself is destructed or cleared.

So, if you have a container of std::unique_ptr elements, they will be destructed for you when appropriate, which in turn will destruct the things that they are pointing at.

Related