实现兼容C++17的可包装任意分配器的诊断跟踪分配器
内存跟踪分配器的适配问题解决方案
问题背景
为兼容标准库的自定义容器编写单元测试时,需要实现一个能适配任意底层分配器的内存跟踪分配器TrackingAllocator,用于验证容器的内存分配/释放计数。当前实现仅支持std::allocator作为底层分配器,且存在两个问题:
- 仍使用C++17已弃用的旧式
rebind结构体 - 无法将底层子分配器正确rebind到新类型
U,导致allocate返回错误类型
核心修正思路
- 移除旧式rebind结构体:C++17及以后,分配器的rebind通过转换构造函数实现,无需显式定义
rebind嵌套结构体 - 传递底层分配器的rebind结果:当需要rebind到类型
U时,同时将底层BaseAllocator也rebind为BaseAllocator::rebind<U>::other - 实现通用转换构造函数:支持从任意
TrackingAllocator<U, BaseAllocatorU>转换到当前类型,保证容器内部rebind时的兼容性 - 维护实例级的跟踪计数:确保分配/释放操作的计数准确,支持测试验证
修正后的完整实现
#include <memory> template <typename T, typename BaseAllocator = std::allocator<T>> struct TrackingAllocator { using value_type = T; // 基础构造函数 TrackingAllocator() = default; TrackingAllocator(const TrackingAllocator&) noexcept = default; // 通用转换构造函数:支持从TrackingAllocator<U, BaseAllocatorU>转换 // BaseAllocatorU自动匹配底层分配器rebind到U后的类型 template <typename U, typename BaseAllocatorU> constexpr TrackingAllocator(const TrackingAllocator<U, BaseAllocatorU>& other) noexcept : m_allocator(other.m_allocator) , allocationCount(other.allocationCount) , deallocationCount(other.deallocationCount) {} // 分配内存并跟踪计数 T* allocate(std::size_t n) { allocationCount += n; return m_allocator.allocate(n); } // 释放内存并跟踪计数 void deallocate(T* p, std::size_t n) noexcept { deallocationCount += n; m_allocator.deallocate(p, n); } // 跟踪计数成员 std::size_t allocationCount = 0; std::size_t deallocationCount = 0; // 暴露底层分配器,供转换构造函数访问 BaseAllocator m_allocator; }; // 标准库分配器要求的相等性比较 template <typename T, typename TA, typename U, typename UA> constexpr bool operator==(const TrackingAllocator<T, TA>& lhs, const TrackingAllocator<U, UA>& rhs) noexcept { return lhs.m_allocator == rhs.m_allocator; } template <typename T, typename TA, typename U, typename UA> constexpr bool operator!=(const TrackingAllocator<T, TA>& lhs, const TrackingAllocator<U, UA>& rhs) noexcept { return !(lhs == rhs); }
关键细节说明
- 转换构造函数:模板参数
BaseAllocatorU自动匹配底层分配器rebind后的类型,比如当BaseAllocator是FixedAllocator<int>,rebind到U时,BaseAllocatorU就是FixedAllocator<U>,保证底层分配器的类型正确 - 无需显式rebind结构体:C++17标准库容器会通过转换构造函数自动处理rebind逻辑,比如
std::vector需要分配内部节点类型时,会自动使用TrackingAllocator<Node, BaseAllocator::rebind<Node>::other>,转换构造函数可直接支持这种场景 - 相等性比较:标准库分配器要求实现
operator==和operator!=,确保容器能正确判断分配器是否相等,避免不必要的内存操作
测试代码兼容性
原测试代码可直接使用修正后的分配器,同时支持嵌套任意底层分配器的场景:
// 示例1:使用默认std::allocator作为底层 TrackingAllocator<int> trackingAllocator; { CustomContainer<TrackingAllocator<int>> container(trackingAllocator); // 执行分配操作 EXPECT_EQ(trackingAllocator.allocationCount, expected_allocs); container.shrink_to_fit(); EXPECT_EQ(trackingAllocator.deallocationCount, expected_deallocs); } // 容器销毁后,分配与释放计数应相等 EXPECT_EQ(trackingAllocator.allocationCount, trackingAllocator.deallocationCount); // 示例2:使用自定义FixedAllocator作为底层 using FixedIntAllocator = FixedAllocator<int>; TrackingAllocator<int, FixedIntAllocator> trackingFixedAlloc; std::vector<int, TrackingAllocator<int, FixedIntAllocator>> vec(trackingFixedAlloc); // 后续测试逻辑同上
内容的提问来源于stack exchange,提问作者Steven
相关产品推荐
相关产品推荐

