C++中shared_ptr多容器存储后的同步删除问题咨询
先看你提供的这段使用shared_ptr管理对象的代码:
struct D { D() { std::cout << "D ctor\n"; } ~D() { std::cout << "D dtor\n"; } }; struct CC { private: std::vector<std::shared_ptr<D>> pDs; static std::shared_ptr<CC> pC; CC() { std::cout << "C ctor\n"; }; public: static std::shared_ptr<CC> GetInstance() { if (nullptr == pC) { pC = std::shared_ptr<CC>(new CC); return pC; } return pC; } void Save(std::shared_ptr<D> d) { pDs.emplace_back(d); std::cout << "saved b\n"; } void Delete(int i) { pDs.erase(pDs.begin() + i); } int Size() { return pDs.size(); } ~CC() { std::cout << "C dtor\n"; } }; std::shared_ptr<CC> CC::pC(nullptr); struct Caller { void Init() { std::shared_ptr<D> d = std::shared_ptr<D>(new D()); CC::GetInstance()->Save(d); } int Size() { return CC::GetInstance()->Size(); } }; struct Deletor { void Delete(int i) { CC::GetInstance()->Delete(i); } };
接下来针对你提出的问题:当多个容器(比如MAP_A和MAP_B)存储同一个shared_ptr实例,删除其中一个容器里的元素时,该如何处理其他容器中对应的实例,我分两种场景来解答:
核心原理先明确
shared_ptr的核心是引用计数:每个shared_ptr都是对原始对象的一个引用,当你把同一个shared_ptr(或通过拷贝构造/赋值得到的新shared_ptr)存入多个容器时,这些shared_ptr都会共享同一个引用计数。
当你调用MAP_A.erase(key_i)时,只是把MAP_A中对应的shared_ptr销毁,引用计数会减1,但只要MAP_B里还有对应的shared_ptr,引用计数就不会降到0,原始对象也不会被销毁。
场景1:只关心对象的自动销毁,不需要同步删除其他容器的元素
这种情况你完全不需要做额外处理。shared_ptr的引用计数机制会自动帮你管理对象生命周期:只有当所有容器里的对应shared_ptr都被erase(或者被销毁),引用计数降到0时,原始对象才会自动调用析构函数销毁。
比如你的示例中:
std::map<int, std::shared_ptr<D>> MAP_A, MAP_B; auto d = std::make_shared<D>(); MAP_A[1] = d; MAP_B[1] = d; MAP_A.erase(1); // 此时d的引用计数从2降到1,对象不会销毁 // MAP_B里的d仍然有效,可以正常使用 MAP_B.erase(1); // 引用计数降到0,对象销毁,调用D的析构函数
场景2:需要同步删除所有容器中对应的元素(即删除一个容器的项,其他容器的对应项也要删掉)
这种需求shared_ptr本身做不到,因为它只负责对象的生命周期管理,不负责跟踪自己被哪些容器存储。你需要额外的机制来实现同步:
方案1:维护一个全局的反向映射表
创建一个结构,记录每个shared_ptr对应的所有容器的信息(比如容器类型、键值)。当你要删除某个容器中的元素时,先查这个反向表,找到所有存储该shared_ptr的容器,然后逐个调用erase。
举个简单的例子:
#include <map> #include <vector> #include <memory> #include <unordered_set> // 假设我们用int作为键,存储shared_ptr<D>的容器有map和vector struct ContainerTracker { // 记录每个shared_ptr<D>对应的所有map的键 std::unordered_map<std::shared_ptr<D>, std::unordered_set<int>> mapKeys; // 记录每个shared_ptr<D>对应的所有vector的索引 std::unordered_map<std::shared_ptr<D>, std::unordered_set<size_t>> vectorIndices; // 维护所有需要同步的map和vector std::vector<std::map<int, std::shared_ptr<D>>*> allMaps; std::vector<std::vector<std::shared_ptr<D>>*> allVectors; // 向map中添加元素时,同时更新跟踪表 void addToMap(std::map<int, std::shared_ptr<D>>& map, int key, std::shared_ptr<D> d) { map[key] = d; mapKeys[d].insert(key); // 如果这个map还没被加入跟踪列表,就添加进去 if (std::find(allMaps.begin(), allMaps.end(), &map) == allMaps.end()) { allMaps.push_back(&map); } } // 删除map中的元素时,同步删除其他容器的对应元素 void eraseFromMap(std::map<int, std::shared_ptr<D>>& map, int key) { auto it = map.find(key); if (it == map.end()) return; auto d = it->second; map.erase(it); // 同步删除其他map中的对应键 for (auto* otherMap : allMaps) { if (otherMap == &map) continue; for (int k : mapKeys[d]) { otherMap->erase(k); } } // 同步删除vector中的对应索引(注意:vector删除元素后索引会变化,这里只是简化示例) for (auto* vec : allVectors) { std::vector<size_t> validIndices; for (size_t idx : vectorIndices[d]) { if (idx < vec->size()) { validIndices.push_back(idx); } } // 倒序删除避免索引失效 std::sort(validIndices.rbegin(), validIndices.rend()); for (size_t idx : validIndices) { vec->erase(vec->begin() + idx); } } // 从跟踪表中移除该shared_ptr的记录 mapKeys.erase(d); vectorIndices.erase(d); } };
注意:这个方案需要你严格通过跟踪表的接口来操作所有容器,否则会出现不同步的情况。
方案2:使用观察者模式
让每个容器成为观察者,当某个容器删除元素时,通知所有其他容器删除对应的元素。或者创建一个统一的管理类,所有容器的添加/删除操作都通过这个类执行,由它负责同步所有容器的状态。
总结
- 如果只是要保证对象自动销毁,不用做任何额外操作,
shared_ptr的引用计数会自动处理。 - 如果需要同步删除所有容器中的对应元素,需要自己实现额外的跟踪机制,比如反向映射表或者观察者模式,确保所有容器的操作都被统一管理。
内容的提问来源于stack exchange,提问作者Hello Everyone

