如何在std::allocate_shared的construct中获取shared_ptr_storage内存
要在construct()和destroy()中访问完整的shared_ptr_storage结构体,核心是利用内存布局的确定性反向推导存储地址——因为std::allocate_shared的内部分配类型(控制块+目标对象)是我们分配的shared_ptr_storage内存的一部分,我们可以通过地址对齐和偏移计算拿到存储结构体的指针。
修改后的完整代码
#include <memory> #include <array> #include <cstdint> #include <iostream> #include <cstdlib> #include <ctime> constexpr std::size_t ceil_to_multiple(std::size_t number, std::size_t multiple) { return number + multiple - 1 - (number + multiple - 1) % multiple; } template <typename T> struct shared_ptr_storage { constexpr static std::size_t align = std::max(alignof(void*), alignof(T)); // 预留足够空间容纳控制块+T对象 constexpr static std::size_t buffer_size = ceil_to_multiple(sizeof(T), align) + 32; // 额外属性的大小(按对齐要求调整) constexpr static std::size_t extra_size = ceil_to_multiple(sizeof(int) + sizeof(bool), align); // 存储控制块和T对象的缓冲区 std::array<std::byte, buffer_size> buffer; // 自定义额外属性 int extra_id; bool is_active; }; template <class T> struct shared_allocator; template <class T, class U> struct shared_allocator_impl { using value_type = U; shared_allocator_impl() = delete; shared_allocator_impl(const shared_allocator<T>&) {} // 确保内部类型U能放入缓冲区 static_assert(sizeof(U) <= shared_ptr_storage<T>::buffer_size, "U exceeds buffer size"); constexpr std::size_t max_size() const noexcept { return 1; } value_type* allocate(std::size_t) { // 分配完整的storage结构体,返回缓冲区起始地址作为U的分配地址 auto* storage = new shared_ptr_storage<T>{}; // 初始化额外属性的默认值 storage->is_active = true; return reinterpret_cast<value_type*>(storage->buffer.data()); } void deallocate(value_type* p, std::size_t) { // 从U的地址反向推导storage地址 std::byte* buffer_ptr = reinterpret_cast<std::byte*>(p); auto* storage = reinterpret_cast<shared_ptr_storage<T>*>( buffer_ptr - offsetof(shared_ptr_storage<T>, buffer) ); delete storage; } }; template <class T> struct shared_allocator { using value_type = T; shared_allocator() = default; constexpr std::size_t max_size() const noexcept { return 1; } template <typename... arguments> void construct(value_type* p, arguments&&... parameters) { // 从T*反向推导storage地址: // 1. T是内部类型U的一部分,U起始地址对齐到shared_ptr_storage<T>::align // 2. 将T*地址向下对齐到该对齐值,得到U的起始地址(即缓冲区起始地址) const std::uintptr_t align = shared_ptr_storage<T>::align; std::uintptr_t p_addr = reinterpret_cast<std::uintptr_t>(p); std::uintptr_t buffer_addr = p_addr - (p_addr % align); // 从缓冲区地址推导storage结构体地址 auto* storage = reinterpret_cast<shared_ptr_storage<T>*>( buffer_addr - offsetof(shared_ptr_storage<T>, buffer) ); // 访问并修改额外属性 storage->extra_id = rand() % 1000; // 示例:随机生成ID std::cout << "Constructing object with extra ID: " << storage->extra_id << std::endl; std::construct_at(p, std::forward<arguments>(parameters)...); } void destroy(value_type* p) { // 同样的方式推导storage地址 const std::uintptr_t align = shared_ptr_storage<T>::align; std::uintptr_t p_addr = reinterpret_cast<std::uintptr_t>(p); std::uintptr_t buffer_addr = p_addr - (p_addr % align); auto* storage = reinterpret_cast<shared_ptr_storage<T>*>( buffer_addr - offsetof(shared_ptr_storage<T>, buffer) ); // 访问额外属性 std::cout << "Destroying object with extra ID: " << storage->extra_id << std::endl; p->~value_type(); } template <typename U> struct rebind{ using other = std::conditional_t<std::is_same_v<T, U>, shared_allocator<T>, shared_allocator_impl<T, U>>; }; }; template <class T, class U> bool operator==(const shared_allocator<T>&, const shared_allocator<U>&) { return true; } template <class T, class U> bool operator!=(const shared_allocator<T>&, const shared_allocator<U>&) { return false; } template <class T, class U> bool operator==(const shared_allocator<T>&, const shared_allocator_impl<T, U>&) { return true; } template <class T, class U> bool operator!=(const shared_allocator<T>&, const shared_allocator_impl<T, U>&) { return false; } int main() { srand(time(nullptr)); shared_allocator<int> allocator; auto i = std::allocate_shared<int>(allocator, 10); auto j = std::allocate_shared<int>(allocator, 20); }
关键实现细节
调整
shared_ptr_storage布局:- 将额外属性(
extra_id、is_active)放在缓冲区之后,确保缓冲区专门用于容纳std::allocate_shared的内部类型(控制块+T)。 - 通过
ceil_to_multiple确保额外属性的大小满足对齐要求,避免内存布局错乱。
- 将额外属性(
分配与释放逻辑:
allocate函数分配完整的shared_ptr_storage结构体,返回缓冲区的起始地址作为内部类型U的分配地址。deallocate函数通过offsetof从U*反向计算出shared_ptr_storage的地址,完成释放。
construct/destroy中的地址推导:- 利用
std::allocate_shared内部类型U的对齐要求不超过shared_ptr_storage<T>::align的特性,将T*地址向下对齐到该值,得到U的起始地址(即缓冲区起始地址)。 - 再通过
offsetof计算缓冲区在shared_ptr_storage中的偏移,最终得到完整的存储结构体指针,从而访问额外属性。
- 利用
这种方式完全基于C++标准特性(offsetof、地址对齐),跨平台可靠性高,无需依赖std::allocate_shared的内部实现细节。
内容的提问来源于stack exchange,提问作者Martijn Otto
相关产品推荐
相关产品推荐

