openMSX
ObjectPool.hh
Go to the documentation of this file.
1#ifndef OBJECTPOOL_HH
2#define OBJECTPOOL_HH
3
4#include <array>
5#include <cstdint>
6#include <memory>
7#include <vector>
8#include "narrow.hh"
9#include "unreachable.hh"
10#include "xrange.hh"
11
12// ObjectPool
13//
14// This is a bit like a traditional pool-allocator, but with these differences:
15// - Instead of only allocating and deallocating memory, this class also
16// constructs/destructs the objects contained in the pool. (But see the
17// remark below about destroying the ObjectPool itself).
18// - The objects in this pool are not accessed via a pointer but instead via an
19// index. That is inserting a object into this pool returns an index. And that
20// index can later be used to retrieve the actual object or to remove the
21// object again. (As an optimization the insert operation additionally also
22// returns a pointer to the object).
23//
24// The choice of identifying objects via a (32-bit) index rather than via a
25// pointer involves some trade-offs:
26// - On a 64-bit system, storing (many) indices only takes half as much space
27// as storing pointers.
28// - Accessing an object via an index is a bit slower than via a pointer.
29// - Removing an object (via an index) is about the same speed as accessing the
30// object. Removing via pointer is not possible.
31//
32// Objects in this pool have stable addresses. IOW once you retrieve a pointer
33// to an object that pointer remains valid. Also when more elements are inserted
34// later. Even if (internally) the pool needs to allocate more memory.
35//
36// Memory is allocated in chunks (in the current implementation a chunk has
37// room for 256 objects). Typically two objects that are inserted one after the
38// other will be next to each other in memory (good for cache-locality). Though
39// this is not a guarantee. When objects are removed, those free locations are
40// re-filled first before allocating new memory.
41//
42// Inserting an object is done via the emplace() function. This will in-place
43// constructs the object in the pool. So you cannot request uninitialized
44// memory from the pool. Removing an object from the pool will call the
45// destructor on that object. However when the ObjectPool itself is destructed
46// it will NOT destruct all objects that are still present in the pool.
47// Depending on the type of object and the state of your program this may or
48// may not be a problem. E.g. maybe the object has a trivial destructor, or
49// maybe it's fine to leak memory on exit.
50//
51// It's not possible to loop over all the objects that are present in the pool.
52
53template<typename T>
55{
56public:
57 using Index = uint32_t;
58
61 T* ptr;
62 };
63
64private:
65 union Element {
66 Element() {}
67 ~Element() {}
68
69 Index next;
70 T t;
71 };
72 using Element256 = std::array<Element, 256>;
73
74public:
75 template<typename... Args>
76 [[nodiscard]] EmplaceResult emplace(Args&& ...args) {
77 Index idx;
78 if (free != Index(-1)) {
79 idx = free;
80 free = get(idx).next;
81 } else {
82 if (cntr == 0) {
83 pool.push_back(std::make_unique<Element256>());
84 }
85 idx = Index(((pool.size() - 1) << 8) + cntr);
86 ++cntr;
87 }
88 T* ptr = &get(idx).t;
89 new(ptr) T(std::forward<Args>(args)...);
90 return {idx, ptr};
91 }
92
93 [[nodiscard]] const T& operator[](Index idx) const { return get(idx).t; }
94 [[nodiscard]] T& operator[](Index idx) { return get(idx).t; }
95
96 void remove(Index idx) {
97 auto& elem = get(idx);
98 elem.t.~T();
99 elem.next = free;
100 free = idx;
101 }
102
103 void remove(const T* ptr) {
104 remove(ptr2Index(ptr));
105 }
106
107 Index ptr2Index(const T* ptr) const {
108 for (auto i : xrange(pool.size())) {
109 const auto& elem256 = *pool[i];
110 const T* begin = &elem256[0].t;
111 const T* end = begin + 256;
112 if ((begin <= ptr) && (ptr < end)) {
113 return Index((i << 8) + (ptr - begin));
114 }
115 }
117 }
118
119 // for unittest
120 [[nodiscard]] Index capacity() const {
121 return narrow<Index>(256 * pool.size());
122 }
123
124private:
125 [[nodiscard]] const Element& get(Index idx) const { return (*pool[idx >> 8])[idx & 255]; }
126 [[nodiscard]] Element& get(Index idx) { return (*pool[idx >> 8])[idx & 255]; }
127
128private:
129 std::vector<std::unique_ptr<Element256>> pool;
130 Index free = Index(-1);
131 uint8_t cntr = 0;
132};
133
134#endif
TclObject t
void remove(const T *ptr)
uint32_t Index
Definition ObjectPool.hh:57
Index capacity() const
Index ptr2Index(const T *ptr) const
void remove(Index idx)
Definition ObjectPool.hh:96
T & operator[](Index idx)
Definition ObjectPool.hh:94
const T & operator[](Index idx) const
Definition ObjectPool.hh:93
EmplaceResult emplace(Args &&...args)
Definition ObjectPool.hh:76
uint32_t next(octet_iterator &it, octet_iterator end)
#define UNREACHABLE
constexpr auto xrange(T e)
Definition xrange.hh:132
constexpr auto begin(const zstring_view &x)
constexpr auto end(const zstring_view &x)