std::unordered_map 的自定义分配器

custom allocator for std::unordered_map

提问人:Jager 提问时间:8/31/2023 最后编辑:Jager 更新时间:8/31/2023 访问量:191

问:

我正在尝试将我的自定义分配器用于 .分配器已经适用于我自己的对象,也适用于,但是当我尝试以相同的方式使用它时,我收到来自 hashtable.h 的错误消息:std::unordered_mapstd::vectorstd::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_mapstd::unordered_map

我知道使用自定义分配器是一种特殊情况。我想使用它,因为我在嵌入式环境中运行,并且想在静态内存上使用 tlsf 内存分配。此外,我希望我的自定义分配器创建的所有对象都在同一tlsf_pool中。因此,我无法给出 a ,因为这会导致该方法返回不同的内存区域。在这一点上,这足以在我的自定义分配器上运行我自己的类。但是,由于我还想分配和覆盖我的自定义分配器,因此我给自己编写了一个分配器适配器 (),用于向 STL 容器提供tlsf_allocator。然后,它还具有模板参数 u,因此也可以使用 value_type = U;。class tlsf_allocatortemplate<typename T>static tlsf_t& get_tlsf_pool();std::vectorstd::unordered_mapstruct allocator_for

这足以分配 ,但我失败了。std::vectorstd::unordered_map

C++ C++20 无序映射 分配器

评论

0赞 Sam Varshavchik 8/31/2023
实际上,分配器参数是什么?显示的代码包含多个相关的模板和类,粗略检查表明它们都不符合分配器的要求。std::unordered_map
0赞 UpAndAdam 8/31/2023
你为什么提到chatGPT?你从中生成了代码吗?

答:

-1赞 Lucas Streanga 8/31/2023 #1

在编译时,通过模板参数向 STL 容器提供分配器。默认情况下,这些参数设置为 STL 分配器,特别是 std::allocator。

STL 允许分配器作为模板参数的原因是模块化的,即您可以获得 std::vector 的行为,而无需与某些分配方案紧密耦合。这包括与 的紧密耦合。new

首先,您不需要覆盖运算符。定义和使用自定义分配器可以解决这个问题。你不应该两者兼而有之。new

在实现自定义分配器之前,您必须问自己,为什么?实现自定义分配器的原因很少。最引人注目的是性能。 和默认实现是非常通用的。这有利于可用性,但在某些利基场景中可能会损害性能。例如,游戏引擎和 Web 服务器等应用程序通常使用竞技场或池分配器。这是因为他们经常在几乎相同的时间分配许多小物体,并且不太在乎它们的寿命。对于通用分配器,这很昂贵,但竞技场分配器可以以非常便宜的价格执行多个分配和释放,但代价是延迟内存回收。std::allocatornew

但是,假设您确实想要实现自定义分配器。来自 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>>();

评论

0赞 Jager 8/31/2023
我调整了我的问题,以解决为什么要使用自定义分配器。感谢您对 cppreference 的引用。我的第一个实现实际上具有分配器模板的完整实现。但是由于我无法将所有对象存储在同一个tlsf_pool中,因此我切换到了 stl 容器的分配器适配器allocator_for。在中间步骤中,我似乎删除了未使用的模板函数。我将立即添加这个,在这个过程中,我还将添加来自 n. m. 的提示,这可能是一个 AI。作为补充:对于 std::vector,我的分配器适配器已经工作
1赞 n. m. could be an AI 8/31/2023 #2

你的是.必须是包含其第一个元素的对。就您的情况而言,这将是.keyconst i_b*value_typeconst keyconst i_b * const

评论

0赞 Jager 8/31/2023
我是否正确理解确保指针始终始终指向同一内存区域,并且第二个 const () 确保指针指向常量值?所以内存区域没有改变?我仍然不太明白,如果我使用 for 键,为什么对中的键部分必须是 .const i_b*i_b * constconst i_b*const i_b * const
0赞 n. m. could be an AI 8/31/2023
否,表示您不能更改指针指向的对象(是非法的)。 表示您不能更改指针本身(是非法的)。这是一个顶级常量。地图的value_type需要具有顶级常量的键。const i_b*i_b*p = another_vari_b * constp = &another_var
0赞 Jager 8/31/2023
啊,与我想象的完全相反。谢谢你的解释。