OXYGENHUB
◆ main openmsx / src / utils / MemBuffer.hh
5.7 KiB · C++ · last changed chore(): initial commit 1 month ago source blame history raw
1
#ifndef MEMBUFFER_HH
2
#define MEMBUFFER_HH
3
4
#include "MemoryOps.hh"
5
6
#include <algorithm>
7
#include <cassert>
8
#include <cstddef>
9
#include <cstdlib>
10
#include <new>      // for bad_alloc
11
#include <span>
12
#include <type_traits>
13
14
namespace openmsx {
15
16
/** This class manages the lifetime of a block of memory.
17
  *
18
  * Its two main use cases are:
19
  *  1) As a safer alternative for new[] / delete[].
20
  *     Using this class makes sure the memory block is always properly
21
  *     cleaned up. Also in case of exceptions.
22
  *  2) As an alternative for vector<byte> ('byte' or other primitive types).
23
  *     The main difference with vector is that the allocated block is left
24
  *     uninitialized. This can be a bit more efficient if the block will
25
  *     anyway soon be overwritten.
26
  *
27
  * Like vector this buffer can dynamically grow or shrink. But it's not
28
  * optimized for this case (it doesn't keep track of extra capacity). If you
29
  * need frequent resizing prefer to use vector instead of this class.
30
  */
31
template<typename T, size_t ALIGNMENT = 0> class MemBuffer
32
{
33
	static_assert(std::is_trivially_copyable_v<T>);
34
	static_assert(std::is_trivially_destructible_v<T>);
35
public:
36
	/** Construct an empty MemBuffer, no memory is allocated.
37
	 */
38
	MemBuffer()
39
		: dat(nullptr)
40
		, sz(0)
41
	{
42
	}
43
44
	/** Construct a (uninitialized) memory buffer of given size.
45
	 */
46
	explicit MemBuffer(size_t size)
47
		: dat(static_cast<T*>(my_malloc(size * sizeof(T))))
48
		, sz(size)
49
	{
50
	}
51
52
	/** Move constructor. */
53
	MemBuffer(MemBuffer&& other) noexcept
54
		: dat(other.dat)
55
		, sz(other.sz)
56
	{
57
		other.dat = nullptr;
58
		other.sz = 0;
59
	}
60
61
	/** Move assignment. */
62
	MemBuffer& operator=(MemBuffer&& other) noexcept
63
	{
64
		std::swap(dat, other.dat);
65
		std::swap(sz , other.sz);
66
		return *this;
67
	}
68
69
	/** Free the memory buffer.
70
	 */
71
	~MemBuffer()
72
	{
73
		my_free(dat);
74
	}
75
76
	/** Returns pointer to the start of the memory buffer.
77
	  * This method can be called even when there's no buffer allocated.
78
	  */
79
	[[nodiscard]] const T* data() const { return dat; }
80
	[[nodiscard]]       T* data()       { return dat; }
81
82
	[[nodiscard]]       T* begin()       { return data(); }
83
	[[nodiscard]] const T* begin() const { return data(); }
84
	[[nodiscard]]       T* end()         { return data() + size(); }
85
	[[nodiscard]] const T* end()   const { return data() + size(); }
86
87
	/** Access elements in the memory buffer.
88
	 */
89
	[[nodiscard]] const T& operator[](size_t i) const
90
	{
91
		assert(i < sz);
92
		return dat[i];
93
	}
94
	[[nodiscard]] T& operator[](size_t i)
95
	{
96
		assert(i < sz);
97
		return dat[i];
98
	}
99
100
	[[nodiscard]] bool empty() const { return sz == 0; }
101
	[[nodiscard]] size_t size() const { return sz; }
102
103
	[[nodiscard]] T& front()
104
	{
105
		assert(!empty());
106
		return dat[0];
107
	}
108
	[[nodiscard]] const T& front() const
109
	{
110
		assert(!empty());
111
		return dat[0];
112
	}
113
	[[nodiscard]] T& back()
114
	{
115
		assert(!empty());
116
		return dat[sz - 1];
117
	}
118
	[[nodiscard]] const T& back() const
119
	{
120
		assert(!empty());
121
		return dat[sz - 1];
122
	}
123
124
	[[nodiscard]] operator std::span<      T>()       { return {data(), size()}; }
125
	[[nodiscard]] operator std::span<const T>() const { return {data(), size()}; }
126
127
	[[nodiscard]] std::span<const T> first(size_t n) const
128
	{
129
		assert(n <= sz);
130
		return std::span{*this}.first(n);
131
	}
132
	[[nodiscard]] std::span<T> first(size_t n)
133
	{
134
		assert(n <= sz);
135
		return std::span{*this}.first(n);
136
	}
137
	[[nodiscard]] std::span<const T> subspan(size_t offset, size_t n = std::dynamic_extent) const
138
	{
139
		assert(offset <= sz);
140
		assert(n == std::dynamic_extent || (offset + n <= sz));
141
		return std::span{*this}.subspan(offset, n);
142
	}
143
	[[nodiscard]] std::span<T> subspan(size_t offset, size_t n = std::dynamic_extent)
144
	{
145
		assert(offset <= sz);
146
		assert(n == std::dynamic_extent || (offset + n <= sz));
147
		return std::span{*this}.subspan(offset, n);
148
	}
149
150
	/** Grow or shrink the memory block.
151
	  * In case of growing, the extra space is left uninitialized.
152
	  * It is possible (even likely) that the memory buffer is copied
153
	  * to a new location after this call, so data() will return a
154
	  * different pointer value.
155
	  */
156
	void resize(size_t size)
157
	{
158
		if (size) {
159
			dat = static_cast<T*>(my_realloc(dat, size * sizeof(T)));
160
			sz = size;
161
		} else {
162
			clear();
163
		}
164
	}
165
166
	/** Free the allocated memory block and set the current size to 0.
167
	 */
168
	void clear()
169
	{
170
		my_free(dat);
171
		dat = nullptr;
172
		sz = 0;
173
	}
174
175
private:
176
	// If the requested alignment is less or equally strict than the
177
	// guaranteed alignment by the standard malloc()-like functions
178
	// we use those. Otherwise we use platform specific functions to
179
	// request aligned memory.
180
	// A valid alternative would be to always use the platform specific
181
	// functions. The only disadvantage is that we cannot use realloc()
182
	// in that case (there are no, not even platform specific, functions
183
	// to realloc memory with bigger than default alignment).
184
	static constexpr bool SIMPLE_MALLOC = ALIGNMENT <= alignof(std::max_align_t);
185
186
	[[nodiscard]] static void* my_malloc(size_t bytes)
187
	{
188
		if constexpr (SIMPLE_MALLOC) {
189
			void* result = malloc(bytes);
190
			if (!result && bytes) throw std::bad_alloc();
191
			return result;
192
		} else {
193
			// already throws bad_alloc in case of error
194
			return MemoryOps::mallocAligned(ALIGNMENT, bytes);
195
		}
196
	}
197
198
	static void my_free(void* p)
199
	{
200
		if constexpr (SIMPLE_MALLOC) {
201
			free(p);
202
		} else {
203
			MemoryOps::freeAligned(p);
204
		}
205
	}
206
207
	[[nodiscard]] void* my_realloc(void* old, size_t bytes)
208
	{
209
		if constexpr (SIMPLE_MALLOC) {
210
			void* result = realloc(old, bytes);
211
			if (!result && bytes) throw std::bad_alloc();
212
			return result;
213
		} else {
214
			void* result = MemoryOps::mallocAligned(ALIGNMENT, bytes);
215
			if (!result && bytes) throw std::bad_alloc();
216
			MemoryOps::freeAligned(old);
217
			return result;
218
		}
219
	}
220
221
private:
222
	T* dat;
223
	size_t sz;
224
};
225
226
} // namespace openmsx
227
228
#endif