C++20:获取std::allocate_shared分配大小的标准合规方法及池分配器适配
1. 分配器适配层:按需匹配对应大小的内存池
由于boost::pool_allocator针对固定大小内存块优化,而std::allocate_shared会一次性分配控制块+T实例的合并内存,我们可以实现一个适配分配器,根据实际请求的字节大小,动态选择或创建对应尺寸的boost::pool_allocator实例:
#include <memory> #include <unordered_map> #include <mutex> #include <boost/pool/pool_alloc.hpp> template <typename T> struct PoolAllocatorAdapter { using value_type = T; PoolAllocatorAdapter() = default; template <typename U> PoolAllocatorAdapter(const PoolAllocatorAdapter<U>&) noexcept {} T* allocate(std::size_t n) { if (n != 1) { // allocate_shared仅会请求单个对象的分配 throw std::bad_alloc(); } static std::mutex mtx; static std::unordered_map<std::size_t, boost::pool_allocator<T>> pools; std::lock_guard<std::mutex> lock(mtx); auto& pool = pools[sizeof(T)]; return pool.allocate(1); } void deallocate(T* ptr, std::size_t n) noexcept { if (n != 1) return; std::lock_guard<std::mutex> lock(mtx); auto it = pools.find(sizeof(T)); if (it != pools.end()) { it->second.deallocate(ptr, 1); } } }; template <typename T, typename... Args> std::shared_ptr<T> make_shared_with_pool(Args&&... args) { return std::allocate_shared<T>(PoolAllocatorAdapter<std::byte>(), std::forward<Args>(args)...); } // 使用示例 struct Foo {}; std::shared_ptr<Foo> create() { return make_shared_with_pool<Foo>(); }
适配层用std::byte作为值类型,让分配请求的大小直接对应allocate_shared需要的总字节数,内部通过哈希表管理不同尺寸的内存池实例。
2. 预估算合并分配的内存尺寸(仅针对特定实现)
虽然标准未定义控制块的结构,但如果你的代码仅针对特定编译器/标准库,可以估算allocate_shared需要的总内存大小,提前创建对应尺寸的boost::pool_allocator:
#include <type_traits> #include <boost/pool/pool_alloc.hpp> #include <memory> struct Foo {}; // 估算控制块+Foo的总大小(需根据目标标准库调整额外开销) using AllocType = std::aligned_storage_t< sizeof(Foo) + 64, // 64为控制块近似额外开销,GCC/libstdc++、Clang/libc++可能不同 std::max(alignof(Foo), alignof(std::shared_ptr<void>)) >; std::shared_ptr<Foo> create() { static boost::pool_allocator<AllocType> alloc; return std::allocate_shared<Foo>(alloc); }
注意:这种方法完全不可移植,更换标准库或编译器版本后必须重新调整额外开销值。
3. 手动分配+自定义删除器(放弃内存合并优化)
如果可以接受控制块与T实例分开分配,直接用boost::pool_allocator分配T,再为std::shared_ptr指定自定义删除器完成内存释放:
#include <boost/pool/pool_alloc.hpp> #include <memory> struct Foo {}; std::shared_ptr<Foo> create() { static boost::pool_allocator<Foo> alloc; Foo* ptr = alloc.allocate(1); try { std::construct_at(ptr); return std::shared_ptr<Foo>(ptr, [](Foo* p) { std::destroy_at(p); alloc.deallocate(p, 1); }); } catch (...) { alloc.deallocate(ptr, 1); throw; } }
这种方式让boost::pool_allocator完全发挥固定尺寸块的优化优势,但失去了allocate_shared的内存合并(减少内存碎片)特性。
关于标准合规的分配大小获取
C++标准将std::allocate_shared的内部控制块结构列为实现细节,没有任何标准接口可以直接获取其合并分配的内存大小。所有依赖特定标准库内部类型(如GCC的_Sp_counted_ptr_inplace、Clang的__shared_ptr_emplace::_Storage)的方法均属于未定义行为,完全不可移植,严禁在生产代码中使用。
内容的提问来源于stack exchange,提问作者Lukas Barth

