使用奇特的指针实现自定义分配器

Implementing a custom allocator with fancy pointers

我正在尝试实现我自己的分配器,它应该与 STL 容器一起使用并使用自定义的奇特指针实现。

我很确定,我的 类 满足所有要求(根据 cppreference),但我的实现无法针对 std::list 进行编译,因为没有从我的奇特指针到普通指针。

一个显示问题的最小示例,(但显然不是我的实际实现):

fancy_ptr.h:

#include <cstddef>

template<typename T>
class FancyPtr {
    T *ptr;

    FancyPtr(T *ptr, bool) : ptr(ptr) {};   //Bool to be non standart

public:
    using element_type = T;

    FancyPtr(std::nullptr_t n) : FancyPtr() {};

    template<class S>
    operator FancyPtr<S>() {
        return {ptr};
    }

    T &operator*() { return *ptr; }

    T &operator[](size_t n) { return ptr[n]; }

    T *operator->() { return ptr; }

    bool operator==(const FancyPtr &other) { return ptr == other.ptr; };

    static FancyPtr pointer_to(element_type &r) { return FancyPtr(&r, false); };
};

TrivialAllocator.h:

#include "fancy_ptr.h"

template<typename T>
class TrivialAllocator {
public:
    using pointer = FancyPtr<T>;
    using value_type = T;

    TrivialAllocator() = default;

    template<typename Other>
    TrivialAllocator(const TrivialAllocator<Other> &other) {};

    template<typename Other>
    TrivialAllocator(TrivialAllocator<Other> &&other) {};

    TrivialAllocator(TrivialAllocator &alloc) = default;

    pointer allocate(size_t n) { return pointer::pointer_to(*new T[n]); }

    void deallocate(pointer ptr, size_t n) { delete[] &*ptr; };

    bool operator==(const TrivialAllocator &rhs) const { return true; };

    bool operator!=(const TrivialAllocator &rhs) const { return false; };
};

main.cpp:

#include "TrivialAllocator.h"
#include <list>

int main() {
    struct Test {};
    using AllocT = std::allocator_traits<TrivialAllocator<long double>>;
    static_assert(std::is_same_v<FancyPtr<long double>,std::pointer_traits<AllocT::pointer>::pointer>);
    static_assert(std::is_same_v<FancyPtr<Test>, std::pointer_traits<AllocT::pointer>::rebind<Test>>);


    std::list<long double, AllocT::allocator_type> list;
}

静态断言没问题。

任何人都可以告诉我我需要做什么才能让它工作吗?

PS:我知道 operator-> 类似于转换运算符,但潜在的问题是 std::list 似乎没有保存我喜欢的指针,而是原始指针。

在深入研究问题之后,我猜想由于 libstdc++ 内部限制,这根本不可能。这是一个已知的老错误,"Node-based containers don't use allocator's pointer type internally":

Container nodes are linked together by built-in pointers, but they should use the allocator's pointer type instead. Currently I think only std::vector does this correctly. ...

它应该与 Clang 和 libc++ 一起工作(使用 -stdlib=libc++ 命令行选项)并进行了一些修复:

  1. FancyPtr<void>::pointer_to(...) 应该是一个有效的成员函数。现在不是了,因为 void& 不存在。
  2. FancyPtr 应该提供 operator!=(...) 成员函数。

需要这些修复以使您的代码至少可以编译。它是否能正常工作超出了这个答案的范围。

您的 class 不满足所有要求。

你的指针类型必须是 Cpp17RandomAccessIterator (operator++, operator+=, operator+, operator--, operator-, operator-=, operator!=, operator<, operator>=, operator<=)

您的 FancyPtr<void>(即 std::allocator_traits<TrivialAllocator<T>>::void_pointer)无法编译,因为 operator[]pointer_to(void&)void_pointerconst_void_pointer不需要是随机访问迭代器)

您不能从 pointer 转换为 void_pointer(您需要将转换运算符更改为 return {static_cast<S*>(ptr);},或者在构造函数上添加 static_cast void_pointer)

您不能将指针类型转换为 bool,这是 NullablePointer 的要求之一。

您的分配器的 allocatedeallocate 不应调用构造函数或析构函数。

然而,即使在使其成为分配器的有效指针类型之后,您仍然 运行 对 libstdc++ 和 libc++ 如何处理指针存在疑问。在代码的某些位置,存在从 FancyPtr<ListNode>FancyPtr<ListNodeBase> 的转换,其中 ListNode 派生自 ListNodeBase。这不是指针类型要求的一部分,但无论如何在那些标准库中由 std::list 的实现使用。您可以通过对使用的任何 T 使用 operator FancyPtr<T> 来允许这样做。如果节点 class 是标准布局,则标准库可以通过转换为空指针然后转换为基 class 来解决此问题。

libstdc++ 还在内部到处使用原始 T* 指针,因此在某些地方它会隐式尝试从 T* 转换为 FancyPtr<T>。不幸的是,支持这一点的唯一方法是拥有一个 public FancyPtr(T*) 构造函数和一个到原始指针的转换 operator T*()。 libstdc++ 可以在不破坏 ABI 的情况下解决这个问题,方法是使用 p ? std::pointer_traits<pointer>::pointer_to(*p) : pointer(nullptr)T* p 转换为奇特的指针,而 std::to_address 则相反。

Microsoft 的 STL std::list 没有有效指针类型的问题。

这是一个奇特指针类型的示例实现,它满足要求并具有此处提到的 2 个标准库实现的变通方法:https://godbolt.org/z/vq9cvW