/* * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * Copyright 2011-2016 Danny Robson */ #ifndef CRUFT_UTIL_POOL_HPP #define CRUFT_UTIL_POOL_HPP #include "nocopy.hpp" #include "debug.hpp" #include #include #include namespace util { /// a simple pre-allocated pool for storage of PODs. /// /// non-POD types can be stored, but there are no guarantees for calling /// item destructors at pool destruction time. template class pool { protected: union node; union alignas (T) node { std::atomic next; char data[sizeof (T)]; }; static_assert (std::atomic::is_always_lock_free); // root address of allocation. used in deletion at destruction time. node* m_head; // the next available entry in the linked list std::atomic m_next; // the total number of items that could be stored const size_t m_capacity; // the number of items currently stored. std::atomic m_size; public: pool (const pool&) = delete; pool& operator= (const pool&) = delete; pool (pool&&); pool& operator= (pool&&); explicit pool (unsigned int _capacity): m_capacity (_capacity), m_size (0u) { // allocate the memory and note the base address for deletion in destructor m_next = m_head = new node[m_capacity]; // build out a complete singly linked list from all the nodes. for (size_t i = 0; i < m_capacity - 1; ++i) m_next[i].next = m_next + i + 1; m_next[m_capacity - 1].next = nullptr; } ~pool () { // don't check if everything's been returned as pools are often used // for PODs which don't need to be destructed via calling release. delete [] m_head; } // Data management [[nodiscard]] T* allocate (void) { // double check we have enough capacity left if (!m_next) throw std::bad_alloc (); CHECK_LT (m_size, m_capacity); // unlink the current cursor do { node* curr = m_next; node* soon = curr->next; if (m_next.compare_exchange_weak (curr, soon)) { ++m_size; return std::launder (reinterpret_cast (curr)); } } while (1); } void deallocate (T *base) { auto soon = reinterpret_cast (base); do { node *curr = m_next; soon->next = curr; if (m_next.compare_exchange_weak (curr, soon)) { --m_size; return; } } while (1); } template T* construct (Args &&...args) { auto ptr = allocate (); try { return new (ptr) T (std::forward (args)...); } catch (...) { deallocate (ptr); throw; } } void destroy (T *ptr) { ptr->~T (); deallocate (ptr); } size_t capacity (void) const { return m_capacity; } size_t size (void) const { return m_size; } bool empty (void) const { return m_size == m_capacity; } // Indexing size_t index (const T*) const; T& operator[] (size_t idx) &; const T& operator[] (size_t idx) const&; }; } #endif