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