C++ freeing memory of nested container and objects

There a great chance this question is duplicated, but I wasn't sure how to do a proper search on this. Also it may be trivial to you but it will help me as a beginner a lot!

Say I have three classes, each has a container

class A {
    std::map<int, B> container1;
};

class B {
    std::unordered_map<int, C> container2;
};

class C {
    std::vector<int> container3;
};

If I have an object 'a' created from class A, with container1/2/3 all filled with data, my questions are the followings:

  1. When I call a.container1.clear(), will all the memory allocated for container2 and container3 be freed? If so, how does C++ do this under the hood?
  2. When the destructor of 'a' is called, will container2 and 3 be freed?
  3. If 1 and 2 are true, do they apply to any nesting combination of containers? For instance here I have map->unordered_map->vector. A different combination could be vector->unordered_map->map.

My gut feeling is that they are all true. Otherwise if I have a very deeply nested structure, it doesn't make sense to go all the way to bottom and explicitly free the memory there.


Solution 1:

The short answer: Yes, all nested containers in this scenario will have their memory freed.

The long answer: Let's work from the top.

First, I'm going to assume that these are structs instead of classes (or that the containers have been declared public); otherwise, a.container1.clear() won't compile, as container1 would be a private member of a. However, putting that aside:

What happens when you call a.container1.clear()? Per https://www.cplusplus.com/reference/map/map/clear/, the clear method removes all elements from the container, and those are destroyed.

So, this implies that each object of type B in a.container1 will be destroyed, i.e. B's destructor will be called.

In C++, when an object of a class is destroyed, the destructor of each member of the class is called after that class's destructor has finished (in this case, B's destructor is the default compiler-generated destructor, as no user-supplied destructor is present). So, after B is destroyed, the destructor of B.container2 will be called. Since container2 is an unordered_map, its destructor will destroy all of its contained elements (https://www.cplusplus.com/reference/unordered_map/unordered_map/~unordered_map/)

Since container2's elements are of type C, each element of type C in container2 will have its destructor called. Again, container3's destructor will be called. And, as a vector, container3's storage capacity will be deallocated, i.e. freed (https://www.cplusplus.com/reference/vector/vector/~vector/).

Now, we've worked our way to the bottom, and we've seen that all memory is freed!