带有自定义分配器/本地分配器的std::unordered_map不编译
std::unordered_map with custom allocator / local allocator does not compile
我有一个非常简单的自定义/本地分配器。我的目标是使用堆栈上的数组作为内存的分配部分。它似乎在std::vector
中工作,但当我尝试将它插入std::unordered_map
时,它无法编译。gcc 7.4.0的错误消息非常难以理解。大致如下:
hashtable_policy.h:2083:26: error: no matching function for call to
‘MonotonicIncreasingAllocator<std::pair<const int, std::string>, 500>::
MonotonicIncreasingAllocator(std::__detail::_Hashtable_alloc<MonotonicIncreasingAllocator
<std::__detail::_Hash_node<std::pair<const int, std::string>, false>, 500> >::
__node_alloc_type&)’
__value_alloc_type __a(_M_node_allocator());
Clang 7.1.0更易于管理。从类似error: no matching conversion for functional-style cast from 'const std::_Hashtable . . .
的错误中滚动我发现:
hashmap_custom_alloc.cpp:11:5: note: candidate constructor not viable: no known conversion from
'MonotonicIncreasingAllocator<std::__detail::_Hash_node<std::pair<const int,
std::__cxx11::basic_string<char> >, false>, [...]>' to 'const
MonotonicIncreasingAllocator<std::__detail::_Hash_node_base *, [...]>' for 1st argument
MonotonicIncreasingAllocator(const MonotonicIncreasingAllocator& rhs) = default;
^
让它更清楚地表明这个std::__detail::_Hash_node_base
位正在妨碍它。以下是代码,两个unordered_map声明都没有编译:
#include <array>
#include <stdexcept>
#include <unordered_map>
#include <vector>
template<class T, std::size_t max_size>
class MonotonicIncreasingAllocator
{
public:
MonotonicIncreasingAllocator() : _index{0} {}
using type = MonotonicIncreasingAllocator<T, max_size>;
using other = MonotonicIncreasingAllocator<T, max_size>;
using value_type = T;
using size_type = std::size_t;
using difference_type = std::ptrdiff_t;
using propagate_on_container_move_assignment = std::true_type;
using is_always_equal = std::true_type;
template<class U>
using rebind = MonotonicIncreasingAllocator<U, max_size>;
T* allocate(std::size_t n)
{
T* r = _data.begin() + _index;
_index += n;
return r;
}
constexpr void deallocate(T* p, std::size_t n)
{
throw std::runtime_error("MontonicIncreasingAllocator can never deallocate()!");
}
private:
std::size_t _index;
std::array<T, max_size> _data;
};
int main()
{
using namespace std;
using key = int;
using value = string;
using item = pair<key, value>;
using alloc = MonotonicIncreasingAllocator<item, 500>;
alloc a0;
alloc a1;
vector<item, alloc> v0(a0);
vector<int, alloc> v1;
// unordered_map<key, value, hash<key>, equal_to<key>, alloc> m; // doesn't compile
// unordered_map<key, value, hash<key>, equal_to<key>, alloc> m(500, a1); // doesn't compile
return 0;
}
T
类型的分配器必须可绑定到U
类型的分配器——这就是为什么有rebind
模板的原因。
要做到这一点,您必须提供一种将构造从类型U
转换为类型T
的方法,例如,从MonotonicIncreasingAllocator<U, ...>&
构造的构造函数,例如:
template <typename U>
MonotonicIncreasingAllocator( const MonotonicIncreasingAllocator<U, max_size>& )
您可能会立即注意到一个问题:array<U,max_size>
不一定要复制到array<T,max_size>
;因此,您需要重新思考分配器的设计[1]
由于遗留的原因;分配器";该模型旨在具有可复制性。这种要求使得很难使用本身包含状态而不是间接指向状态的分配器。
注意:这可能对vector
有效的原因是,类型为T
的分配器不会在vector<T>
上反弹,因为它只需要分配T
的n
实例。对于更复杂的数据结构,如map
、set
、unordered_map
等,情况并非如此,因为可能存在对象的节点或内部使用的其他连续序列。
[1]有状态分配器直接存储在使用它们的容器中。这意味着vector<T,MonotonicIncreasingAllocator<T,N>>
现在还将直接在vector
类内部存储分配器本身,其中包含array<T,N>
,以及它自己的数据——这是浪费。使用此分配器复制甚至移动容器将是一项极其昂贵的操作。
此外,通过将数据直接存储在分配器内部,转换构造需要整个内部std::array
对象的副本,这意味着重新绑定构造了一个新对象,该对象引用了与正在反弹的分配器不同的单调结构,这并不理想。
您应该查看std::pmr::polymorphic_allocator
中使用的体系结构以获得更好的灵感。std::pmr::polymorphic_allocator
保留1种数据类型:std::memory_resource
指针,这使得重新绑定变得便宜,并且该分配器的存储也很便宜。memory_resource
是类型不明确的,通过间接传递,这允许分配器在反弹后使用和引用同一内存池。
正如@Human Compiler在回答中所说,不应该将分配的数据与分配器耦合。解决方案相当简单:从堆栈上所需的数组中传入指针。您不必为在这个线程和其他地方发现的所有分配器包装器的废话而烦恼。在SOLID术语中,数据作为依赖项注入分配器。
我仍然觉得重新绑定界面非常奇怪。这显然是我们一直坚持的糟糕设计。除了编写struct rebind { other...
陈旧别名之外,还必须提供反弹类型的复制构造函数。后者几乎没有记录,如果有的话。
#include <array>
#include <unordered_map>
#include <vector>
struct SharedArray
{
uint8_t* data;
uint64_t index;
};
template<class T>
class MonotonicIncreasingAllocator
{
public:
MonotonicIncreasingAllocator(SharedArray& a) : _data{a} {}
template<class U>
MonotonicIncreasingAllocator(const MonotonicIncreasingAllocator<U>& rhs)
: _data{const_cast<MonotonicIncreasingAllocator<U>&>(rhs).data()} {}
using type = MonotonicIncreasingAllocator<T>;
using other = MonotonicIncreasingAllocator<T>;
using value_type = T;
using size_type = std::size_t;
using difference_type = std::ptrdiff_t;
using propagate_on_container_move_assignment = std::true_type;
using is_always_equal = std::true_type;
template<class U>
using rebind = MonotonicIncreasingAllocator<U>;
T* allocate(std::size_t n)
{
T* r = _data.data + _data.index;
_data.index += n * sizeof(T);
return r;
}
constexpr void deallocate(T* p, std::size_t n)
{
return;
}
SharedArray& data()
{
return _data;
}
private:
SharedArray& _data;
};
int main()
{
using namespace std;
using key = int;
using value = string;
using item = pair<key, value>;
std::array<uint8_t, 4096> arr; // allocate enough, here but a page
SharedArray sharr;
sharr.index = 0;
sharr.data = arr.begin();
using alloc = MonotonicIncreasingAllocator<item>;
alloc a0(sharr);
alloc a1(sharr);
vector<item, alloc> v0(a0);
unordered_map<key, value, hash<key>, equal_to<key>, alloc> m(500, a1);
return 0;
}
- 当有分配器意识的容器被复制/移动时,反弹分配器是否被复制/移走
- 将 std::allocate_shared 与多态资源分配器一起使用
- 尝试将lambda函数放在队列中时出现一般分配器错误(可能是与unique_ptr有关的错误)
- C++17 - 使用自定义分配器的节点提取/重新插入 - 适用于 clang++/libc++,但不适用于 libstd
- 使用 std::分配器在 constexpr 中进行默认初始化
- 使用不兼容的分配器复制分配无序列图
- C++:矢量分配器行为、内存分配和智能指针
- 是否可以使用分配器对象来释放另一个分配器分配的内存?
- 使用模板化分配器和对向量进行排序的函数
- C++ 中的分配器错误
- 基于浅树的数据结构的内存分配器,用于频繁分配和解除分配
- 通过引用传递向量是请求 std::分配器
- 为什么这个分配器不适用于"std::allocate_shared"?奇怪的模板替换错误
- 为什么 std::vector 使用 std::分配器而不是运算符 new 和 delete?
- C++自定义分配器大小参数作为模板参数会引发编译器错误
- 在分配器中销毁元素时,C++会导致双重释放<string>?
- C++17 和更新的 std::分配器是否适用于动态数量的自定义堆?
- c++ 中的自定义分配器,用于不调用secure_string实现
- 是否允许分配器构造和销毁成员函数从内部逻辑引发异常?
- 我可以对 std::array 使用自定义分配器来获取安全加密密钥吗?