使用花哨的指针实现自定义分配器

问题描述

我正在尝试实现我自己的分配器,它应该与 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"

容器节点通过内置指针链接在一起,但它们应该使用分配器的指针类型。目前我认为只有 std::vector 可以正确执行此操作。 ...

它应该适用于 Clang 和 libc++(使用 -stdlib=libc++ 命令行选项),并进行了一些修复:

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

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

,

您的课程没有满足所有要求。

您的指针类型必须是 Cpp17RandomAccessIterator(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 不t 需要是随机访问迭代器)

您无法从 pointer 转换为 void_pointer(您需要将转换运算符更改为 return {static_cast<S*>(ptr);},或者在 static_cast 的构造函数上使用 void_pointer {1}})

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

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

然而,即使将其设为分配器的有效指针类型,您仍然会遇到 libstdc++ 和 libc++ 如何处理指针的问题。在代码中的某些点,有从 FancyPtr<ListNode>FancyPtr<ListNodeBase> 的强制转换,其中 ListNode 派生自 ListNodeBase。这不是指针类型要求的一部分,但无论如何都由这些标准库中的 std::list 实现使用。您可以通过为任何使用的 operator FancyPtr<T> 设置 T 来允许此操作。如果节点类是标准布局,标准库可能能够通过转换为空指针然后转换为基类来解决此问题。

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

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

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