5
\$\begingroup\$

This is an implementation of C++17 std::allocator under the name my_std::allocator, with deprecated stuff omitted for simplicity.

// A implementation of C++17 std::allocator
// with deprecated members omitted

#include <cstddef>
#include <new>
#include <type_traits>

namespace my_std {

  template <typename T>
  class allocator {
  public:
    using value_type = T;
    using propagate_on_container_move_assignment = std::true_type;
    using is_always_equal = std::true_type;

    allocator() noexcept = default;
    allocator(const allocator&) noexcept = default;
    ~allocator() = default;

    template <class U>
    allocator(const allocator<U>&) noexcept
    {
    }

    T* allocate(std::size_t n)
    {
      if constexpr (alignof(T) > __STDCPP_DEFAULT_NEW_ALIGNMENT__)
        return static_cast<T*>(
          ::operator new(n * sizeof(T), static_cast<std::align_val_t>(alignof(T)))
        );
      else
        return static_cast<T*>(
          ::operator new(n * sizeof(T))
        );
    }

    void deallocate(T* p, std::size_t n)
    {
      if constexpr (alignof(T) > __STDCPP_DEFAULT_NEW_ALIGNMENT__)
        return ::operator delete(p, n * sizeof(T),
                                 static_cast<std::align_val_t>(alignof(T)));
      else
        return ::operator delete(p, n * sizeof(T));      
    }
  };

  template <class T, class U>
  bool operator==(const allocator<T>&, const allocator<U>&) noexcept
  {
    return true;
  }

  template <class T, class U>
  bool operator!=(const allocator<T>&, const allocator<U>&) noexcept
  {
    return false;
  }

}

I have tested it like this:

int main()
{
  std::set<std::string, std::less<std::string>,
       my_std::allocator<std::string>> words;

  for (std::string w; std::cin >> w;)
    words.insert(std::move(w));

  for (const auto& w : words)
    std::cout << w << "\n";
}

And currently no bug is discovered.

\$\endgroup\$
5
\$\begingroup\$
  1. noexcept on the explicitly defaulted default-ctor and copy-ctor is already implied.
  2. Copy-ctor and dtor would be implicitly declared if left out.
  3. .allocate() (and also .deallocate(), but that doesn't matter) fails to check n * sizeof(T) for wrap-around.
    Possible Solutions:

    1. Start with a check. This has the advantage of simplicity and broadest conformity.

      if (n > std::size_t(-1) / sizeof(T))
          throw std::bad_array_new_length();
      
    2. Use saturating math, and rely on ::operator new being unable to allocate std::size_t(-1) bytes on (nearly?) all implementations. This is generally more efficient for the common case.

      auto bytes = n > std::size_t(-1) / sizeof(T) ? std::size_t(-1) : n * sizeof(T);
      
  4. Because the casts are so simple and obviously right, I would eschew the verbosity of static_cast. Guidelines may prohibit it though...

  5. I suggest importing the following additional refinements from C++20:

    1. Mark the templated ctor constexpr.
    2. Mark .allocate()'s return-value [[nodiscard]].
    3. Mark both operator== and operator!= constexpr.
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.