aboutsummaryrefslogtreecommitdiff
path: root/libbutl/small-allocator.hxx
diff options
context:
space:
mode:
Diffstat (limited to 'libbutl/small-allocator.hxx')
-rw-r--r--libbutl/small-allocator.hxx181
1 files changed, 181 insertions, 0 deletions
diff --git a/libbutl/small-allocator.hxx b/libbutl/small-allocator.hxx
new file mode 100644
index 0000000..429ba41
--- /dev/null
+++ b/libbutl/small-allocator.hxx
@@ -0,0 +1,181 @@
+// file : libbutl/small-allocator.hxx -*- C++ -*-
+// license : MIT; see accompanying LICENSE file
+
+#pragma once
+
+#include <cassert>
+#include <cstddef> // size_t
+#include <utility> // move()
+#include <type_traits> // true_type, is_same
+
+#include <libbutl/export.hxx>
+
+namespace butl
+{
+ // Implementation of the allocator (and its buffer) for small containers.
+ //
+ template <typename T, std::size_t N>
+ struct small_allocator_buffer
+ {
+ using value_type = T;
+
+ // Note that the names are decorated in order not to conflict with
+ // the container's interface.
+
+ // If free_ is true then the buffer is not allocated.
+ //
+ alignas (alignof (value_type)) char data_[sizeof (value_type) * N];
+ bool free_ = true;
+
+ // Note that the buffer should be constructed before the container and
+ // destroyed after (since the container's destructor will be destroying
+ // elements potentially residing in the buffer). This means that the
+ // buffer should be inherited from and before the std:: container.
+ //
+ small_allocator_buffer () = default;
+
+ small_allocator_buffer (small_allocator_buffer&&) = delete;
+ small_allocator_buffer (const small_allocator_buffer&) = delete;
+
+ small_allocator_buffer& operator= (small_allocator_buffer&&) = delete;
+ small_allocator_buffer& operator= (const small_allocator_buffer&) = delete;
+ };
+
+ template <typename T,
+ std::size_t N,
+ typename B = small_allocator_buffer<T, N>>
+ class small_allocator
+ {
+ public:
+ using buffer_type = B;
+
+ explicit
+ small_allocator (buffer_type* b) noexcept: buf_ (b) {}
+
+ // Allocator interface.
+ //
+ public:
+ using value_type = T;
+
+ // These shouldn't be required but as usual there are old/broken
+ // implementations (like std::list in GCC 4.9).
+ //
+ using pointer = value_type*;
+ using const_pointer = const value_type*;
+ using reference = value_type&;
+ using const_reference = const value_type&;
+
+ static void destroy (T* p) {p->~T ();}
+
+ template <typename... A>
+ static void construct (T* p, A&&... a)
+ {
+ ::new (static_cast<void*> (p)) T (std::forward<A> (a)...);
+ }
+
+ // Allocator rebinding.
+ //
+ // We assume that only one of the rebound allocators will actually be
+ // doing allocations and that its value type is the same as buffer value
+ // type. This is needed, for instance, for std::list since what actually
+ // gets allocated is the node type, not T (see small_list for details).
+ //
+ template <typename U>
+ struct rebind {using other = small_allocator<U, N, B>;};
+
+ template <typename U>
+ explicit
+ small_allocator (const small_allocator<U, N, B>& x) noexcept
+ : buf_ (x.buf_) {}
+
+ T*
+ allocate (std::size_t n)
+ {
+ // An implementation can rebind the allocator to something completely
+ // different. For example, VC15u3 with _ITERATOR_DEBUG_LEVEL != 0
+ // allocates some extra stuff which cannot possibly come from the static
+ // buffer.
+ //
+ if (std::is_same<T, typename buffer_type::value_type>::value)
+ {
+ if (buf_->free_)
+ {
+ assert (n >= N); // We should never be asked for less than N.
+
+ if (n == N)
+ {
+ buf_->free_ = false;
+ return reinterpret_cast<T*> (buf_->data_);
+ }
+ // Fall through.
+ }
+ }
+
+ return static_cast<T*> (::operator new (sizeof (T) * n));
+ }
+
+ void
+ deallocate (void* p, std::size_t) noexcept
+ {
+ if (p == buf_->data_)
+ buf_->free_ = true;
+ else
+ ::operator delete (p);
+ }
+
+ friend bool
+ operator== (small_allocator x, small_allocator y) noexcept
+ {
+ // We can use y to deallocate x's allocations if they use the same small
+ // buffer or neither uses its small buffer (which means all allocations,
+ // if any, have been from the shared heap).
+ //
+ // Things get trickier with rebinding. If A is allocator and B is its
+ // rebinding, then the following must hold true:
+ //
+ // A a1(a) => a1==a
+ // A a(b) => B(a)==b && A(b)==a
+ //
+ // As a result, the rebinding constructor above always copies the buffer
+ // pointer and we decide whether to use the small buffer by comparing
+ // allocator/buffer value types.
+ //
+ // We also expect that any copy of the original allocator made by the
+ // std:: implementation of the container is temporary (that is, it
+ // doesn't outlive the small buffer).
+ //
+ return (x.buf_ == y.buf_) || (x.buf_->free_ && y.buf_->free_);
+ }
+
+ friend bool
+ operator!= (small_allocator x, small_allocator y) noexcept
+ {
+ return !(x == y);
+ }
+
+ // It might get instantiated but should not be called.
+ //
+ small_allocator
+ select_on_container_copy_construction () const noexcept
+ {
+ assert (false);
+ return small_allocator (nullptr);
+ }
+
+ // propagate_on_container_copy_assignment = false
+ // propagate_on_container_move_assignment = false
+
+ // Swap is not supported (see explanation in small_vector::swap()).
+ //
+ using propagate_on_container_swap = std::true_type;
+
+ void
+ swap (small_allocator&) = delete;
+
+ private:
+ template <typename, std::size_t, typename>
+ friend class small_allocator; // For buffer access in rebind.
+
+ buffer_type* buf_; // Must not be NULL.
+ };
+}