提问人:Jager 提问时间:8/31/2023 最后编辑:Jager 更新时间:8/31/2023 访问量:191
std::unordered_map 的自定义分配器
custom allocator for std::unordered_map
问:
我正在尝试将我的自定义分配器用于 .分配器已经适用于我自己的对象,也适用于,但是当我尝试以相同的方式使用它时,我收到来自 hashtable.h 的错误消息:std::unordered_map
std::vector
std::unordered_map
/usr/include/c++/11/bits/hashtable.h:204:21: error: static assertion failed: unordered container must have the same value_type as its allocator
204 | static_assert(is_same<typename _Alloc::value_type, _Value>{},
我尝试使用自定义分配器的类:
class a {
public:
/**
* @brief Creates a A managed by an shared ptr.
*/
[[nodiscard]] static std::shared_ptr<a> create();
/**
* @brief Creates a B as part of A.
* @tparam T The type of the b. (There are different Bs, but all inherit from the origin class B)
* @param args Arguments to pass to b.
* @return The b.
*/
template<typename T, typename... Args>
T* create_b(Args&&... args) {
std::unique_ptr<T> b = std::make_unique<T>(std::forward<Args>(args)...);
Bs_.push_back(std::move(b));
return static_cast<T*>(Bs_.back().get());
}
/**
* @brief Overloaded new operator to use the custom allocator.
*/
void* operator new(std::size_t size)
{
tlsf_allocator allocator;
return allocator.allocate<a>(size);
}
/**
* @brief Overloaded delete operator to use the custom deallocator.
*/
void operator delete(void* ptr)
{
tlsf_allocator allocator;
allocator.deallocate<a>(static_cast<a*>(ptr), 1);
}
private:
// CTOR is private to prevent constructing a class object without being managed by a shared_ptr.
a();
//std::unordered_map<const i_b*, c*, std::hash<const i_b*>, std::equal_to<const i_b*>> Bs_to_Cs_{};//working fine, but no custom allocator
std::unordered_map<const i_b*, c*, std::hash<const i_b*>, std::equal_to<const i_b*>, tlsf_allocator::allocator_for<std::pair<const i_b*, c*>>> Bs_to_Cs_{}; // Error
std::vector<std::unique_ptr<i_b>, tlsf_allocator::allocator_for<std::unique_ptr<i_b>>> Bs_{};//working fine, even with custom allocator
};
我的自定义分配器:
/**
* @class tlsf_allocator
* @brief A custom allocator class that uses TLSF (Two-Level Segregated Fit) for memory allocation.
*/
class tlsf_allocator {
public:
/**
* @brief Allocates memory for 'n' elements of type 'T'.
*
* @tparam T The type of elements to allocate memory for.
* @param n The number of elements to allocate memory for.
* @return A pointer to the allocated memory block.
*
* This function allocates memory for 'n' elements of type 'T' using TLSF memory management.
* It returns a pointer to the allocated memory block.
*/
template<typename T>
T* allocate(size_t n) {
T* result = reinterpret_cast<T*>(tlsf_malloc(get_tlsf_pool(), sizeof(T) * n));
if (result == nullptr) {
throw std::bad_alloc(); // not enough memory to allocate new the object
}
return result;
}
/**
* @brief Deallocates memory block at the given pointer.
*
* @tparam T The type of the memory block being deallocated.
* @param ptr A pointer to the memory block to deallocate.
* @param n The number of elements in the memory block (unused in this implementation), but required for
* std::allocator_traits.
*
* This function deallocates the memory block at the given pointer using TLSF memory management.
* The 'n' parameter is unused in this implementation but kept for compatibility with the allocator concept.
*/
template<typename T>
void deallocate(T* ptr, [[maybe_unused]] size_t n) {
tlsf_free(get_tlsf_pool(), ptr);
}
/**
* @struct allocator_for
* @brief An allocator adaptor for providing the tlsf_allocator to STL containers.
*
* This allocator adaptor is used to provide the tlsf_allocator functionality to STL containers
* like std::vector. It is used as a template parameter when declaring a container with the
* desired element type. This allows the container's memory allocation and deallocation operations
* to be managed by the tlsf_allocator.
*
* @tparam U The type of the elements that the allocator should allocate memory for.
*/
template<typename U>
struct allocator_for {
/**
* @brief Type alias for the element type managed by the allocator adaptor.
*
* This type alias defines the type of elements that the allocator adaptor manages.
* It is used to provide information about the element type to the STL containers
* that use this allocator.
*/
using value_type = U;
/**
* @brief Default constructor.
*/
allocator_for() noexcept = default;
/**
* @brief Copy constructor.
*
* @tparam V Another type.
* @param other Another allocator_for instance.
*/
template<typename V>
explicit allocator_for([[maybe_unused]] const allocator_for<V>& other) noexcept {}
/**
* @brief Allocate memory for elements.
*
* @param n The number of elements to allocate memory for.
* @return A pointer to the allocated memory block.
*/
U* allocate(std::size_t n) {
tlsf_allocator allocator;
return reinterpret_cast<U*>(allocator.allocate<U>(n));
}
/**
* @brief Deallocate memory for elements.
*
* @param p A pointer to the memory block to deallocate.
* @param n The number of elements.
*/
void deallocate(U* p, std::size_t n) noexcept {
tlsf_allocator allocator;
allocator.deallocate<U>(p, n);
}
};
private:
/**
* @brief Gets the TLSF memory pool.
*
* @return A reference to the TLSF memory pool.
*
* This static function returns a reference to the TLSF memory pool used by the allocator.
* It ensures that a single memory pool is shared among all instances of the allocator.
*/
static tlsf_t& get_tlsf_pool();
};
我看过示例代码。每次都使用,但似乎对我不起作用。看起来我的分配器似乎以某种方式使用了错误的value_type。pair<const key, value>
std::unordered_map
我还尝试编写自己的 unordered_map 类,该类在内部使用,但重新定义了 new 和 delete。我知道通常编写自己的 unordered_map 类对于使用自定义分配器是没有必要的,但是由于我的第一次尝试失败了,我想尝试将其作为第二种方法。错误是一样的,所以我放弃了这种方法。std::unordered_map
std::unordered_map
我知道使用自定义分配器是一种特殊情况。我想使用它,因为我在嵌入式环境中运行,并且想在静态内存上使用 tlsf 内存分配。此外,我希望我的自定义分配器创建的所有对象都在同一tlsf_pool中。因此,我无法给出 a ,因为这会导致该方法返回不同的内存区域。在这一点上,这足以在我的自定义分配器上运行我自己的类。但是,由于我还想分配和覆盖我的自定义分配器,因此我给自己编写了一个分配器适配器 (),用于向 STL 容器提供tlsf_allocator。然后,它还具有模板参数 u,因此也可以使用 value_type = U;。class tlsf_allocator
template<typename T>
static tlsf_t& get_tlsf_pool();
std::vector
std::unordered_map
struct allocator_for
这足以分配 ,但我失败了。std::vector
std::unordered_map
答:
在编译时,通过模板参数向 STL 容器提供分配器。默认情况下,这些参数设置为 STL 分配器,特别是 std::allocator。
STL 允许分配器作为模板参数的原因是模块化的,即您可以获得 std::vector 的行为,而无需与某些分配方案紧密耦合。这包括与 的紧密耦合。new
首先,您不需要覆盖运算符。定义和使用自定义分配器可以解决这个问题。你不应该两者兼而有之。new
在实现自定义分配器之前,您必须问自己,为什么?实现自定义分配器的原因很少。最引人注目的是性能。 和默认实现是非常通用的。这有利于可用性,但在某些利基场景中可能会损害性能。例如,游戏引擎和 Web 服务器等应用程序通常使用竞技场或池分配器。这是因为他们经常在几乎相同的时间分配许多小物体,并且不太在乎它们的寿命。对于通用分配器,这很昂贵,但竞技场分配器可以以非常便宜的价格执行多个分配和释放,但代价是延迟内存回收。std::allocator
new
但是,假设您确实想要实现自定义分配器。来自 Java 等语言,您可能认为分配器必须实现一些分配器接口。当使用运行时多态性时,C++ 通常也是如此。但如上所述,分配器是在编译时通过模板参数提供的。
因此,您需要一个模板化的编译时接口。在 C++20 中,它被形式化为 .这与 OOP 样式的接口有着根本的不同。如何?接口必须显式实现,概念则不然。隐式碰巧满足概念要求的类将被接受。因此,要实现自定义分配器,无需从任何接口派生。相反,您只需编写一个类,该类具有 STL 中分配器所期望的必要方法、类型、返回等。concept
请参阅以下链接:cppreference:https://en.cppreference.com/w/cpp/named_req/Allocator
这是要在 STL 中使用的分配器的命名要求(概念)。它甚至包括一个 MVP 示例,您可以直接使用。Cppreference 是 STL 类型内容的绝佳资源。STL 和标准是......复杂。Cppreference 非常注重细节和准确性。
对于您的特定错误,错误消息会很好地将其列出。对于给定容器,分配器的值类型应等于 T。
template<typename T>
custom_allocator
{
using value_type = T;
...
};
auto v = std::vector<int, custom_allocator<int>>();
评论
你的是.必须是包含其第一个元素的对。就您的情况而言,这将是.key
const i_b*
value_type
const key
const i_b * const
评论
const i_b*
i_b * const
const i_b*
const i_b * const
const i_b*
i_b
*p = another_var
i_b * const
p = &another_var
评论
std::unordered_map