我可以在使用 std::make_shared 后缓存吗?

Can I cache after using std::make_shared?

我正在阅读 Effective Modern C++ (Scott Meyers) 并尝试第 21 项中的内容。书中说使用 std::make_shared 的副作用是在所有 shared_ptr 之前无法释放内存并且 weak_ptrs 不见了(因为控制块是和内存一起分配的)。

我预计这将意味着如果我保留一个缓存来容纳一堆 weak_ptr,那么将永远不会释放任何内存。我使用下面的代码尝试了这个,但是当 shared_ptrs 从向量中删除时,我可以使用 pmap 看到内存实际上被释放了。谁能解释我哪里出错了?还是我的理解有误?

注:函数loadWidget与书本上的不一样,为了这个实验的目的

#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;
}

的确,当您使用 std::make_shared 时,新对象和控制块的存储被分配为单个块,因此只要存在 std::weak_ptr 给它。但是,当最后一个 std::shared_ptr 被销毁时,对象仍然被销毁(它的析构函数运行并且它的成员被销毁)。它只是关联的存储空间仍处于分配状态且未被占用。

std::vector 为其元素动态分配存储空间。此存储在 std::vector 外部,它不是对象内存表示的一部分。当您销毁 Widget 时,您也会销毁其 std::vector 成员。该成员的析构函数将释放用于存储其元素的动态分配的内存。唯一不能立即释放的内存是控制块和 Widget 的存储(应该是 sizeof(Widget) 字节)。它不会阻止立即释放向量元素的存储。