如何为std容器的增删操作添加钩子?子类化std::map如何捕获相关操作?
方案建议:优先用组合而非公开继承实现需求
首先明确:STL 标准容器的设计目标不包含公开子类化,std::map 没有虚析构函数,公开继承后如果用基类指针指向子类对象,销毁时会出现未定义行为,因此不建议直接公开子类化std::map。
你提到的重写所有插入删除方法容易遗漏的问题,完全可以通过组合封装的方式彻底避免,这也是最稳妥的实现方案:
- 把
std::map作为自定义类的私有成员变量 - 只对外暴露你实际需要用到的接口,所有涉及元素增删的接口在转发给内部
std::map执行前后,插入你需要的捕获逻辑 - 不需要用到的
std::map接口默认不暴露,从根源上避免出现漏改的修改入口
如果你的业务需要自定义容器完全兼容std::map的所有非修改接口,可以改用私有继承std::map的实现方式,用using声明直接导出不需要修改的接口,只需要手动实现增删相关的接口即可,相比全部重写工作量小很多,也不会遗漏。
示例代码(组合实现)
#include <map> #include <utility> template <typename Key, typename T, typename Compare = std::less<Key>, typename Allocator = std::allocator<std::pair<const Key, T>>> class CustomMap { private: std::map<Key, T, Compare, Allocator> inner_map; // 自定义插入钩子 void on_insert(const Key& key, const T& value) { // 这里写你需要的插入后逻辑 } // 自定义删除钩子 void on_erase(const Key& key) { // 这里写你需要的删除后逻辑 } public: // 普通插入接口 std::pair<typename std::map<Key,T>::iterator, bool> insert(const std::pair<const Key, T>& value) { auto res = inner_map.insert(value); if (res.second) { on_insert(value.first, value.second); } return res; } // 重载[]运算符 T& operator[](const Key& key) { // 注意:[]如果key不存在会插入默认构造的value,需要触发钩子 bool exists = inner_map.count(key); T& val = inner_map[key]; if (!exists) { on_insert(key, val); } return val; } // 删除接口 typename std::map<Key,T>::size_type erase(const Key& key) { auto cnt = inner_map.erase(key); if (cnt > 0) { on_erase(key); } return cnt; } // 其他不需要修改的接口按需转发,比如查找、size、empty等 typename std::map<Key,T>::iterator find(const Key& key) { return inner_map.find(key); } typename std::map<Key,T>::size_type size() const { return inner_map.size(); } bool empty() const { return inner_map.empty(); } // 其他需要的接口依次添加即可 };
补充说明
如果你确实有必须公开继承std::map的场景(比如需要把自定义类传入接收std::map引用的现有接口),也可以在公开继承后,把所有增删相关的接口标记为final避免被子类重写,同时仔细检查所有会修改容器的接口:
- 插入类:
insert、insert_or_assign、emplace、emplace_hint、try_emplace、operator[]、merge - 删除类:
erase、clear、extract - 赋值类:
operator=、swap
只要覆盖了上述所有修改接口,也能实现钩子逻辑的注入,只是维护成本远高于组合方案。
内容的提问来源于stack exchange,提问作者Serge Roussak
相关产品推荐
相关产品推荐

