如何解决使用花哨的指针实现自定义分配器
我正在尝试实现我自己的分配器,它应该与 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++
命令行选项),并进行了一些修复:
-
FancyPtr<void>::pointer_to(...)
应该是一个有效的成员函数。现在不是,因为void&
不存在。 -
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_pointer
和 const_void_pointer
不t 需要是随机访问迭代器)
您无法从 pointer
转换为 void_pointer
(您需要将转换运算符更改为 return {static_cast<S*>(ptr);}
,或者在 static_cast
的构造函数上使用 void_pointer
{1}})
您不能将指针类型转换为 bool
,这是 NullablePointer 的要求之一。
您的分配器的 allocate
和 deallocate
不应调用构造函数或析构函数。
然而,即使将其设为分配器的有效指针类型,您仍然会遇到 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
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。