std::unordered_map 的自定义分配器

问题描述 投票:0回答:1

我正在尝试使用我的自定义分配器来实现

std::unordered_map
。分配器已经适用于我自己的对象以及
std::vector
,但是当我尝试以相同的方式将其用于
std::unordered_map
时,我从 hashtable.h 收到一条错误消息:

/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();
};

我已经查看了示例代码。每次都使用

pair<const key, value>
,但似乎对我不起作用。看起来我的分配器似乎以某种方式为
std::unordered_map
使用了错误的 value_type。

我还尝试编写自己的unordered_map类,它在内部使用

std::unordered_map
,但重新定义了new和delete。错误是一样的,所以我放弃了这个方法。

到目前为止我不知道还能尝试什么。即使 ChatGPT 作为咨询实例也没有给我带来新的输入。

c++ c++20 unordered-map allocator
1个回答
0
投票

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

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

new
的紧密耦合。

首先,您不需要重写运算符

new
。定义和使用自定义分配器可以解决这个问题。你不应该两者都做。

在实现自定义分配器之前,您必须问自己,为什么?实现自定义分配器的理由很少。最引人注目的是性能。

std::allocator
new
的默认实现非常通用。这有利于可用性,但在某些利基场景中可能会损害性能。例如,游戏引擎和 Web 服务器等应用程序通常使用竞技场或池分配器。这是因为它们经常在几乎同一时间分配许多小对象,并且不太关心它们的生命周期。对于通用分配器来说,这是昂贵的,但是 arena 分配器可以非常便宜地执行多次分配和释放,但代价是延迟内存回收。

但是假设您确实想要实现自定义分配器。来自 Java 等语言,您可能期望分配器必须实现某些 Allocator 接口。当使用运行时多态性时,对于 C++ 通常也是如此。但如上所述,分配器是在编译时通过模板参数提供的。

所以你需要一个模板化的编译时接口。在 C++20 中,这被形式化为

concept
。这与 OOP 风格的接口有根本的不同。如何?接口必须显式实现,而概念则不然。隐式碰巧满足概念要求的类会被接受。因此,要实现自定义分配器,您不需要从任何接口派生。相反,您只需编写一个类,该类具有 STL 中分配器所需的必要方法、类型、返回值等。

请参阅 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>>();

© www.soinside.com 2019 - 2024. All rights reserved.