List.h 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286
  1. 
  2. // zlib open source license
  3. //
  4. // Copyright (c) 2018 to 2025 David Forsgren Piuva
  5. //
  6. // This software is provided 'as-is', without any express or implied
  7. // warranty. In no event will the authors be held liable for any damages
  8. // arising from the use of this software.
  9. //
  10. // Permission is granted to anyone to use this software for any purpose,
  11. // including commercial applications, and to alter it and redistribute it
  12. // freely, subject to the following restrictions:
  13. //
  14. // 1. The origin of this software must not be misrepresented; you must not
  15. // claim that you wrote the original software. If you use this software
  16. // in a product, an acknowledgment in the product documentation would be
  17. // appreciated but is not required.
  18. //
  19. // 2. Altered source versions must be plainly marked as such, and must not be
  20. // misrepresented as being the original software.
  21. //
  22. // 3. This notice may not be removed or altered from any source
  23. // distribution.
  24. #ifndef DFPSR_COLLECTION_LIST
  25. #define DFPSR_COLLECTION_LIST
  26. #include "collections.h"
  27. #include "../base/Handle.h"
  28. #include "../base/DsrTraits.h"
  29. #include <cstdint>
  30. #include <utility>
  31. namespace dsr {
  32. // TODO:
  33. // * Allow storing names of lists in debug mode for better error messages, using the same setName method as used in Handle.
  34. // * Allow getting a SafePointer to all elements for faster loops without bound checks in release mode.
  35. // Because elements are returned by reference, the list can not know when an element is modified.
  36. // Therefore it must clone the entire content when assigned by value for consistent behavior during reallocation.
  37. // When cloning on assignment, const can be inherited from the outside for passing read only lists as const references.
  38. template <typename T>
  39. class List {
  40. private:
  41. T *impl_elements = nullptr;
  42. intptr_t impl_length = 0;
  43. intptr_t impl_buffer_length = 0;
  44. // Makes sure that there is memory available for storing at least minimumAllocatedLength elements.
  45. void impl_reserve(intptr_t minimumAllocatedLength) {
  46. if (minimumAllocatedLength > this->impl_buffer_length) {
  47. // Create a new memory allocation.
  48. UnsafeAllocation newAllocation = (heap_allocate(minimumAllocatedLength * sizeof(T), true));
  49. #ifdef SAFE_POINTER_CHECKS
  50. heap_setAllocationName(newAllocation.data, "List allocation");
  51. #endif
  52. T *newElements = (T*)(newAllocation.data);
  53. heap_increaseUseCount(newAllocation.header);
  54. // Use all available space.
  55. uintptr_t availableSize = heap_getAllocationSize(newAllocation.header);
  56. heap_setUsedSize(newAllocation.header, availableSize);
  57. // Move the data from the old allocation to the new allocation.
  58. if (std::is_move_constructible<T>::value) {
  59. // If T is move constructible, we do not have to clone the elements.
  60. for (intptr_t e = 0; e < this->impl_buffer_length; e++) {
  61. new (newElements + e) T(std::move(this->impl_elements[e]));
  62. }
  63. } else {
  64. // If T is not move constructible, we have to create a copy and then destroy the original.
  65. for (intptr_t e = 0; e < this->impl_buffer_length; e++) {
  66. new (newElements + e) T(this->impl_elements[e]);
  67. this->impl_elements[e].~T();
  68. }
  69. }
  70. // Transfer ownership to the new allocation.
  71. heap_decreaseUseCount(this->impl_elements);
  72. this->impl_elements = newElements;
  73. this->impl_buffer_length = availableSize / sizeof(T);
  74. }
  75. }
  76. void impl_setLength(intptr_t newLength) {
  77. if (newLength < this->impl_length) {
  78. for (intptr_t e = newLength; e < this->impl_length; e++) {
  79. // In-place descruction.
  80. this->impl_elements[e].~T();
  81. }
  82. } else {
  83. this->impl_reserve(newLength);
  84. }
  85. this->impl_length = newLength;
  86. }
  87. template<typename... ARGS>
  88. void impl_setElements(ARGS&&... args) {
  89. intptr_t elementCount = sizeof...(args);
  90. this->impl_reserve(elementCount);
  91. this->impl_length = elementCount;
  92. std::initializer_list<T> otherArguments = { T(args)... };
  93. for (intptr_t e = 0; e < elementCount; e++) {
  94. new (this->impl_elements + e) T(otherArguments.begin()[e]);
  95. }
  96. }
  97. public:
  98. // Copy constructor
  99. List(const List<T>& source) {
  100. this->impl_setLength(source.length());
  101. for (intptr_t e = 0; e < source.length(); e++) {
  102. new (this->impl_elements + e) T(source.impl_elements[e]);
  103. }
  104. }
  105. // Move constructor
  106. List(List<T>&& source) noexcept {
  107. // No pre-existing content when move constructing.
  108. this->impl_elements = source.impl_elements;
  109. this->impl_length = source.impl_length;
  110. this->impl_buffer_length = source.impl_buffer_length;
  111. source.impl_elements = nullptr;
  112. source.impl_length = 0;
  113. source.impl_buffer_length = 0;
  114. }
  115. // Copy assignment operator
  116. List& operator = (const List<T>& source) {
  117. if (this != &source) {
  118. // Delete any pre-existing content.
  119. this->impl_setLength(0);
  120. // Clone the content.
  121. this->impl_setLength(source.length());
  122. for (intptr_t e = 0; e < source.length(); e++) {
  123. new (this->impl_elements + e) T(source.impl_elements[e]);
  124. }
  125. }
  126. return *this;
  127. }
  128. // Move assignment operator
  129. List& operator = (List<T>&& source) {
  130. if (this != &source) {
  131. // Delete any pre-existing content when move assigning.
  132. this->impl_setLength(0);
  133. heap_decreaseUseCount(this->impl_elements);
  134. // Move the content.
  135. this->impl_elements = source.impl_elements;
  136. this->impl_length = source.impl_length;
  137. this->impl_buffer_length = source.impl_buffer_length;
  138. source.impl_elements = nullptr;
  139. source.impl_length = 0;
  140. source.impl_buffer_length = 0;
  141. }
  142. return *this;
  143. }
  144. // Constructors
  145. List() {}
  146. template<
  147. typename FIRST,
  148. typename... OTHERS,
  149. DSR_ENABLE_IF(DSR_CONVERTIBLE_TO(FIRST, T))
  150. >
  151. List(FIRST first, OTHERS... others) {
  152. this->impl_setElements(first, others...);
  153. }
  154. ~List() {
  155. // Destroy all elements.
  156. this->impl_setLength(0);
  157. // Free the allocation.
  158. heap_decreaseUseCount(this->impl_elements);
  159. this->impl_elements = nullptr;
  160. this->impl_buffer_length = 0;
  161. }
  162. // Post-condition: Returns the element at index from the range 0..length-1.
  163. T& operator[] (intptr_t index) {
  164. impl_baseZeroBoundCheck(index, this->impl_length, "List index");
  165. return this->impl_elements[index];
  166. }
  167. const T& operator[] (intptr_t index) const {
  168. impl_baseZeroBoundCheck(index, this->impl_length, "List index");
  169. return this->impl_elements[index];
  170. }
  171. // Post-condition: Returns the number of elements in the array list.
  172. inline intptr_t length() const {
  173. return this->impl_length;
  174. }
  175. // Side-effect: Makes sure that the buffer have room for at least minimumLength elements.
  176. // Warning! Reallocation may invalidate old pointers and references to elements in the replaced buffer.
  177. void reserve(intptr_t minimumLength) {
  178. impl_reserve(minimumLength);
  179. }
  180. // Post-condition: Returns an index to the first element, which is always zero.
  181. // Can be used for improving readability when used together with lastIndex.
  182. intptr_t firstIndex() const { return 0; }
  183. // Post-condition: Returns an index to the last element.
  184. intptr_t lastIndex() const { return this->impl_length - 1; }
  185. // Post-condition: Returns a reference to the first element.
  186. T& first() {
  187. impl_nonZeroLengthCheck(this->impl_length, "Length");
  188. return this->impl_elements[0];
  189. }
  190. // Post-condition: Returns a reference to the first element.
  191. const T& first() const {
  192. impl_nonZeroLengthCheck(this->impl_length, "Length");
  193. return this->impl_elements[0];
  194. }
  195. // Post-condition: Returns a reference to the last element.
  196. T& last() {
  197. impl_nonZeroLengthCheck(this->impl_length, "Length");
  198. return this->impl_elements[this->lastIndex()];
  199. }
  200. // Post-condition: Returns a reference to the last element.
  201. const T& last() const {
  202. impl_nonZeroLengthCheck(this->impl_length, "Length");
  203. return this->impl_elements[this->lastIndex()];
  204. }
  205. // Side-effect: Removes all elements by setting the count to zero.
  206. void clear() {
  207. this->impl_setLength(0);
  208. }
  209. // Side-effect: Swap the order of two elements.
  210. // Useful for moving and sorting elements.
  211. void swap(intptr_t indexA, intptr_t indexB) {
  212. impl_baseZeroBoundCheck(indexA, this->impl_length, "Swap index A");
  213. impl_baseZeroBoundCheck(indexB, this->impl_length, "Swap index B");
  214. std::swap(this->impl_elements[indexA], this->impl_elements[indexB]);
  215. }
  216. T& push(const T& newValue) {
  217. this->impl_setLength(this->impl_length + 1);
  218. // Copy construction.
  219. new (&(this->last())) T(newValue);
  220. return this->last();
  221. }
  222. // Side-effect: Pushes a new element at the end.
  223. // Warning! Reallocation may invalidate old pointers and references to elements in the replaced buffer.
  224. // Post-condition: Returns an index to the new element in the list.
  225. intptr_t pushGetIndex(const T& newValue) {
  226. this->impl_setLength(this->impl_length + 1);
  227. // Copy construction.
  228. new (&(this->last())) T(newValue);
  229. return this->lastIndex();
  230. }
  231. // Side-effect: Pushes a new element constructed using the given arguments.
  232. // Warning! Reallocation may invalidate old pointers and references to elements in the replaced buffer.
  233. // Post-condition: Returns a reference to the new element in the list.
  234. template<typename... ARGS>
  235. T& pushConstruct(ARGS&&... args) {
  236. this->impl_setLength(this->impl_length + 1);
  237. // Copy construction.
  238. new (&(this->last())) T(std::forward<ARGS>(args)...);
  239. return this->last();
  240. }
  241. // Side-effect: Pushes a new element constructed using the given arguments.
  242. // Warning! Reallocation may invalidate old pointers and references to elements in the replaced buffer.
  243. // Post-condition: Returns an index to the new element in the list.
  244. template<typename... ARGS>
  245. intptr_t pushConstructGetIndex(ARGS&&... args) {
  246. this->impl_setLength(this->impl_length + 1);
  247. new (&(this->last())) T(std::forward<ARGS>(args)...);
  248. return this->lastIndex();
  249. }
  250. // TODO: Implement constant time remove with swap for not preserving any order.
  251. // Side-effect: Deletes the element at removedIndex without changing the order of other elements.
  252. // Pre-condition: Returns true on success and false on failure.
  253. bool remove(intptr_t removedIndex) {
  254. if (0 <= removedIndex && removedIndex < this->impl_length) {
  255. // Shift all following elements without cloning, which will call the destructor for the removed element.
  256. for (intptr_t e = removedIndex; e < this->impl_length - 1; e++) {
  257. // Move assignment.
  258. this->impl_elements[e] = std::move(this->impl_elements[e + 1]);
  259. }
  260. this->impl_length--;
  261. return true;
  262. } else {
  263. return false;
  264. }
  265. }
  266. // Side-effect: Deletes the last element.
  267. // Pre-condition: Returns true on success and false on failure.
  268. bool pop() {
  269. if (this->impl_length > 0) {
  270. //impl_elements[this->impl_length - 1].~T();
  271. this->impl_setLength(this->impl_length - 1);
  272. return true;
  273. } else {
  274. return false;
  275. }
  276. }
  277. };
  278. }
  279. #endif