I am reading Effective Modern C++ (Scott Meyers) and trying out something from item 21. The book says a side effect of using std::make_shared is that memory cannot be freed until all shared_ptrs and weak_ptrs are gone (because the control block is allocated together with the memory).
I expected that this would mean that if I keep a cache around holding a bunch of weak_ptrs that no memory would ever be freed. I tried this using the code below, but as the shared_ptrs are removed from the vector, I can see using pmap that memory is actually being freed. Can anyone explain me where I am going wrong? Or if my understanding is wrong?
Note: the function loadWidget is not the same as in the book for the purpose of this experiment.
#include <iostream>
#include <memory>
#include <unordered_map>
#include <vector>
#include <thread>
#include <chrono>
class Widget {
public:
Widget()
: values(1024*1024, 3.14)
{ }
std::vector<double> values;
};
std::shared_ptr<Widget> loadWidget(unsigned id) {
return std::make_shared<Widget>();
}
std::unordered_map<unsigned, std::weak_ptr<Widget>> cache;
std::shared_ptr<Widget> fastLoadWidget(unsigned id) {
auto objPtr = cache[id].lock();
if (!objPtr) {
objPtr = loadWidget(id);
cache[id] = objPtr;
}
return objPtr;
}
int main() {
std::vector<std::shared_ptr<Widget>> widgets;
for (unsigned i=0; i < 20; i++) {
std::cout << "Adding widget " << i << std::endl;
widgets.push_back(fastLoadWidget(i));
std::this_thread::sleep_for(std::chrono::milliseconds(500));
}
while (!widgets.empty()) {
widgets.pop_back();
std::this_thread::sleep_for(std::chrono::milliseconds(500));
}
return 0;
}
It is true that when you use
std::make_sharedthe storage for the new object and for the control block is allocated as a single block, so it is not released as long as there exists astd::weak_ptrto it. But, when the laststd::shared_ptris destroyed the object is nonetheless destroyed (its destructor runs and its members are destroyed). It's just the associated storage which remains allocated and unoccupied.std::vectorallocates storage dynamically for its elements. This storage is external to thestd::vector, it is not part of the object's memory representation. When you destroy aWidgetyou also destroy itsstd::vectormember. That member's destructor will release the dynamically allocated memory used to store its elements. The only memory that can't be release immediately is the control block and the storage forWidget(which should besizeof(Widget)bytes). It does not prevent the storage for the elements of the vector from being released immediately.