You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在std::allocate_shared的construct中获取shared_ptr_storage内存

解决方案:在自定义分配器中关联std::allocate_shared的存储结构体

要在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);
}

关键实现细节

  1. 调整shared_ptr_storage布局:

    • 将额外属性(extra_id、is_active)放在缓冲区之后,确保缓冲区专门用于容纳std::allocate_shared的内部类型(控制块+T)。
    • 通过ceil_to_multiple确保额外属性的大小满足对齐要求,避免内存布局错乱。
  2. 分配与释放逻辑:

    • allocate函数分配完整的shared_ptr_storage结构体,返回缓冲区的起始地址作为内部类型U的分配地址。
    • deallocate函数通过offsetof从U*反向计算出shared_ptr_storage的地址,完成释放。
  3. 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.17 11:40:52