std::allocate_shared搭配自定义分配器能否维持单次内存分配?测试未达预期
好问题!首先明确一点:std::allocate_shared本身的设计目标就是支持单次内存分配——和std::make_shared一样,它会请求一块足够容纳对象实例+引用计数控制块的连续内存,然后通过你提供的自定义分配器一次性分配这块内存,最后分别在内存的不同区域构造控制块和对象。
那为什么你的测试代码没达成效果?大概率是自定义分配器的实现踩了坑,我帮你梳理几个常见问题和解决思路:
常见问题排查
1. 自定义分配器未正确处理对齐要求
引用计数控制块和你的对象可能有不同的对齐需求,std::allocate_shared会计算两者的最大对齐值,然后向分配器请求满足该对齐的内存。如果你的分配器:
- 在C++17及以后版本中没有处理
std::align_val_t类型的对齐参数; - 在C++11/14中没有手动处理高对齐要求的内存分配;
就可能导致分配器无法提供符合要求的连续内存,进而触发隐式的多次分配(或者直接运行时错误)。
2. 分配器对大内存请求做了拆分
有些自定义分配器为了避免内存碎片,会对超过阈值的内存请求自动拆分分配。而std::allocate_shared请求的内存大小是对象大小 + 控制块大小,这个总大小可能超过了你分配器的拆分阈值,导致它分多次分配内存。
3. 测试代码的追踪方式有误
如果你只追踪了对象构造时的内存分配,而没关注控制块的分配,就会误以为是多次分配——实际上控制块和对象是在同一块连续内存里的,你需要在分配器的allocate和deallocate方法中加日志,看整个过程是否只调用了一次allocate。
正确实现的示例
这里给你一个简单的追踪式分配器,搭配std::allocate_shared就能看到单次分配的效果:
#include <memory> #include <iostream> // 自定义追踪分配器,用char作为value_type以便分配任意大小内存 struct TrackingAllocator { using value_type = char; TrackingAllocator() = default; template <typename U> TrackingAllocator(const TrackingAllocator<U>&) noexcept {} // C++17及以后支持align_val_t参数处理对齐 char* allocate(std::size_t size, std::align_val_t align = std::align_val_t(alignof(char))) { std::cout << "[Allocator] 单次分配 " << size << " 字节,对齐要求:" << static_cast<std::size_t>(align) << '\n'; return static_cast<char*>(operator new(size, align)); } void deallocate(char* ptr, std::size_t size, std::align_val_t align = std::align_val_t(alignof(char))) { std::cout << "[Allocator] 单次释放 " << size << " 字节\n"; operator delete(ptr, size, align); } }; // 分配器相等性比较(必须实现) template <typename T, typename U> bool operator==(const TrackingAllocator<T>&, const TrackingAllocator<U>&) noexcept { return true; } template <typename T, typename U> bool operator!=(const TrackingAllocator<T>&, const TrackingAllocator<U>&) noexcept { return false; } // 测试用的对象 struct MyObject { int data; MyObject(int val) : data(val) { std::cout << "[Object] 构造完成\n"; } }; int main() { std::cout << "===== 开始测试 std::allocate_shared =====\n"; auto ptr = std::allocate_shared<MyObject>(TrackingAllocator<>(), 42); std::cout << "对象值:" << ptr->data << '\n'; std::cout << "===== 测试结束 =====\n"; return 0; }
运行这段代码,你会看到allocate只被调用一次,分配的大小是sizeof(MyObject)加上std::shared_ptr控制块的大小,这就证明了单次内存分配的效果。
总结
只要你的自定义分配器能正确处理任意大小的内存请求和对应的对齐要求,std::allocate_shared就可以实现和std::make_shared一样的单次内存分配优化,完全不需要用到boost::intrusive这类侵入式方案。如果你的测试还是有问题,建议先检查分配器的对齐处理和内存请求逻辑~
内容的提问来源于stack exchange,提问作者Ayub

