使用奇特的指针实现自定义分配器
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++
命令行选项)并进行了一些修复:
FancyPtr<void>::pointer_to(...)
应该是一个有效的成员函数。现在不是了,因为 void&
不存在。
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_pointer
和 const_void_pointer
不需要是随机访问迭代器)
您不能从 pointer
转换为 void_pointer
(您需要将转换运算符更改为 return {static_cast<S*>(ptr);}
,或者在构造函数上添加 static_cast
void_pointer
)
您不能将指针类型转换为 bool
,这是 NullablePointer 的要求之一。
您的分配器的 allocate
和 deallocate
不应调用构造函数或析构函数。
然而,即使在使其成为分配器的有效指针类型之后,您仍然 运行 对 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
我正在尝试实现我自己的分配器,它应该与 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++
命令行选项)并进行了一些修复:
FancyPtr<void>::pointer_to(...)
应该是一个有效的成员函数。现在不是了,因为void&
不存在。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_pointer
和 const_void_pointer
不需要是随机访问迭代器)
您不能从 pointer
转换为 void_pointer
(您需要将转换运算符更改为 return {static_cast<S*>(ptr);}
,或者在构造函数上添加 static_cast
void_pointer
)
您不能将指针类型转换为 bool
,这是 NullablePointer 的要求之一。
您的分配器的 allocate
和 deallocate
不应调用构造函数或析构函数。
然而,即使在使其成为分配器的有效指针类型之后,您仍然 运行 对 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