对齐 unique_ptr 的负载数据

Payload data with aligned unique_ptr

我有 unique_ptr<T>,它始终与 4096 对齐 - 意味着指针值的最低 12 位始终等于 0。我的意图是在指针中存储一个字节的有效负载数据/元数据本身”。我的意思是创建一个 class 它将有一个移动转换构造函数,如:

template<typename T>
struct PayloadPtr {
    /* ... */
    PayloadPTr( unique_ptr<T> ptr ) noexcept { ... }
    /* ... */
};

这将 'sink' unique_ptr<T> 并让它存储为成员,但重复使用最低的 12 位来存储字节变量...总体布局很重要,正如硬件给出的那样。

{
    uint32_t payload : 12;
    uint32_t pointer : 20;
}

整个 32 位值可以作为屏蔽有效负载的指针读取,可以屏蔽最高 20 位读出有效负载...重要的是 T 的 constructor/destructor 在存储期间不会被调用PayloadPtr,就像常规的unique_ptr成员变量存储一样。

如何实现这种行为?要使用具有 unique_ptr 作为一个成员同时将有效负载作为另一个成员的联合?或者将其存储为纯数据(例如 uintptr_t)并重新解释 getters/setters?

中的数据

推荐的实施方式是什么?感谢任何愿意提供帮助的人!

您可以创建类似于 unique_ptr 的内容,但将指针存储为 std::uintptr_t 并在需要时投射和过滤掉有效载荷。

下面是它的大概样子:

#ifndef __cpp_aligned_new
#error "Requires Dynamic memory allocation for over-aligned data (http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0035r4.html)"
#else

#include <cstdint>
#include <utility>

template<typename T>
class PayloadPtr {
public:
    static_assert(alignof(T) % 0x1000 == 0, "must be aligned to a multiple of 0x1000");

    // construct from a T*
    PayloadPtr(T* t) : data(reinterpret_cast<std::uintptr_t>(t)) {}

    // take over ownershipt from a unique_ptr
    PayloadPtr(std::unique_ptr<T>&& o) : PayloadPtr(o.release()) {}

    // rule of 5
    PayloadPtr(const PayloadPtr&) = delete;
    PayloadPtr(PayloadPtr&& o) noexcept : data(std::exchange(o.data, 0)) {}
    PayloadPtr& operator=(const PayloadPtr&) = delete;
    PayloadPtr& operator=(PayloadPtr&& o) noexcept {
        std::swap(data, o.data);
        return *this;
    }
    ~PayloadPtr() { delete get(); }

    T* get() const { return reinterpret_cast<T*>(data & ~0xFFFULL); }
    T* release() { T* rv = get(); data = 0; return rv; }

    // dereferencing 
    T& operator*() const { return *get(); }
    T* operator->() const { return get(); }

    explicit operator bool() const noexcept { return get() != nullptr; }

    void reset(T* o = nullptr) noexcept {
        delete get();
        data = reinterpret_cast<std::uintptr_t>(o);
    }

    void set_payload(std::uint16_t p) { data = (data & ~0xFFFULL) | (p & 0xFFF); }
    std::uint16_t get_payload() const { return data & 0xFFF; }

private:
    std::uintptr_t data;
};

#endif

Demo