List.h 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285
  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. // The compiler should automatically call a copy constructor if the move operator is deleted.
  59. for (intptr_t e = 0; e < this->impl_buffer_length; e++) {
  60. new (newElements + e) T(std::move(this->impl_elements[e]));
  61. }
  62. // Transfer ownership to the new allocation.
  63. heap_decreaseUseCount(this->impl_elements);
  64. this->impl_elements = newElements;
  65. this->impl_buffer_length = availableSize / sizeof(T);
  66. }
  67. }
  68. void impl_setLength(intptr_t newLength) {
  69. if (newLength < this->impl_length) {
  70. for (intptr_t e = newLength; e < this->impl_length; e++) {
  71. // In-place descruction.
  72. this->impl_elements[e].~T();
  73. }
  74. } else {
  75. this->impl_reserve(newLength);
  76. }
  77. this->impl_length = newLength;
  78. }
  79. template<typename... ARGS>
  80. void impl_setElements(ARGS&&... args) {
  81. intptr_t elementCount = sizeof...(args);
  82. this->impl_reserve(elementCount);
  83. this->impl_length = elementCount;
  84. intptr_t e = 0;
  85. (void)std::initializer_list<int>{
  86. (
  87. new (this->impl_elements + e) T(std::move(args)), e++, 0
  88. )...
  89. };
  90. }
  91. public:
  92. // Copy constructor
  93. List(const List<T>& source) {
  94. this->impl_setLength(source.length());
  95. for (intptr_t e = 0; e < source.length(); e++) {
  96. new (this->impl_elements + e) T(source.impl_elements[e]);
  97. }
  98. }
  99. // Move constructor
  100. List(List<T>&& source) noexcept {
  101. // No pre-existing content when move constructing.
  102. this->impl_elements = source.impl_elements;
  103. this->impl_length = source.impl_length;
  104. this->impl_buffer_length = source.impl_buffer_length;
  105. source.impl_elements = nullptr;
  106. source.impl_length = 0;
  107. source.impl_buffer_length = 0;
  108. }
  109. // Copy assignment operator
  110. List& operator = (const List<T>& source) {
  111. if (this != &source) {
  112. // Delete any pre-existing content.
  113. this->impl_setLength(0);
  114. // Clone the content.
  115. this->impl_setLength(source.length());
  116. for (intptr_t e = 0; e < source.length(); e++) {
  117. new (this->impl_elements + e) T(source.impl_elements[e]);
  118. }
  119. }
  120. return *this;
  121. }
  122. // Move assignment operator
  123. List& operator = (List<T>&& source) {
  124. if (this != &source) {
  125. // Delete any pre-existing content when move assigning.
  126. this->impl_setLength(0);
  127. heap_decreaseUseCount(this->impl_elements);
  128. // Move the content.
  129. this->impl_elements = source.impl_elements;
  130. this->impl_length = source.impl_length;
  131. this->impl_buffer_length = source.impl_buffer_length;
  132. source.impl_elements = nullptr;
  133. source.impl_length = 0;
  134. source.impl_buffer_length = 0;
  135. }
  136. return *this;
  137. }
  138. // Constructors
  139. List() {}
  140. template<
  141. typename FIRST,
  142. typename... OTHERS,
  143. DSR_ENABLE_IF(DSR_CONVERTIBLE_TO(FIRST, T))
  144. >
  145. List(FIRST first, OTHERS&&... others) {
  146. this->impl_setElements(first, others...);
  147. }
  148. ~List() {
  149. // Destroy all elements.
  150. this->impl_setLength(0);
  151. // Free the allocation.
  152. heap_decreaseUseCount(this->impl_elements);
  153. this->impl_elements = nullptr;
  154. this->impl_buffer_length = 0;
  155. }
  156. // Post-condition: Returns the element at index from the range 0..length-1.
  157. T& operator[] (intptr_t index) {
  158. impl_baseZeroBoundCheck(index, this->impl_length, "List index");
  159. return this->impl_elements[index];
  160. }
  161. const T& operator[] (intptr_t index) const {
  162. impl_baseZeroBoundCheck(index, this->impl_length, "List index");
  163. return this->impl_elements[index];
  164. }
  165. // Post-condition: Returns the number of elements in the array list.
  166. inline intptr_t length() const {
  167. return this->impl_length;
  168. }
  169. // Side-effect: Makes sure that the buffer have room for at least minimumLength elements.
  170. // Warning! Reallocation may invalidate old pointers and references to elements in the replaced buffer.
  171. void reserve(intptr_t minimumLength) {
  172. impl_reserve(minimumLength);
  173. }
  174. // Post-condition: Returns an index to the first element, which is always zero.
  175. // Can be used for improving readability when used together with lastIndex.
  176. intptr_t firstIndex() const { return 0; }
  177. // Post-condition: Returns an index to the last element.
  178. intptr_t lastIndex() const { return this->impl_length - 1; }
  179. // Post-condition: Returns a reference to the first element.
  180. T& first() {
  181. impl_nonZeroLengthCheck(this->impl_length, "Length");
  182. return this->impl_elements[0];
  183. }
  184. // Post-condition: Returns a reference to the first element.
  185. const T& first() const {
  186. impl_nonZeroLengthCheck(this->impl_length, "Length");
  187. return this->impl_elements[0];
  188. }
  189. // Post-condition: Returns a reference to the last element.
  190. T& last() {
  191. impl_nonZeroLengthCheck(this->impl_length, "Length");
  192. return this->impl_elements[this->lastIndex()];
  193. }
  194. // Post-condition: Returns a reference to the last element.
  195. const T& last() const {
  196. impl_nonZeroLengthCheck(this->impl_length, "Length");
  197. return this->impl_elements[this->lastIndex()];
  198. }
  199. // Side-effect: Removes all elements by setting the count to zero.
  200. void clear() {
  201. this->impl_setLength(0);
  202. }
  203. // Side-effect: Swap the order of two elements.
  204. // Useful for moving and sorting elements.
  205. void swap(intptr_t indexA, intptr_t indexB) {
  206. impl_baseZeroBoundCheck(indexA, this->impl_length, "Swap index A");
  207. impl_baseZeroBoundCheck(indexB, this->impl_length, "Swap index B");
  208. std::swap(this->impl_elements[indexA], this->impl_elements[indexB]);
  209. }
  210. T& push(const T& newValue) {
  211. this->impl_setLength(this->impl_length + 1);
  212. // Copy construction.
  213. new (&(this->last())) T(newValue);
  214. return this->last();
  215. }
  216. // Side-effect: Pushes a new element at the end.
  217. // Warning! Reallocation may invalidate old pointers and references to elements in the replaced buffer.
  218. // Post-condition: Returns an index to the new element in the list.
  219. intptr_t pushGetIndex(const T& newValue) {
  220. this->impl_setLength(this->impl_length + 1);
  221. // Copy construction.
  222. new (&(this->last())) T(newValue);
  223. return this->lastIndex();
  224. }
  225. // Side-effect: Pushes a new element constructed using the given arguments.
  226. // Warning! Reallocation may invalidate old pointers and references to elements in the replaced buffer.
  227. // Warning! Do not pass an element in the list as an argument to the constructor,
  228. // because reallocating will move the data from that location before being sent to the constructor.
  229. // Post-condition: Returns a reference to the new element in the list.
  230. template<typename... ARGS>
  231. T& pushConstruct(ARGS&&... args) {
  232. this->impl_setLength(this->impl_length + 1);
  233. // Copy construction.
  234. new (&(this->last())) T(std::forward<ARGS>(args)...);
  235. return this->last();
  236. }
  237. // Side-effect: Pushes a new element constructed using the given arguments.
  238. // Warning! Reallocation may invalidate old pointers and references to elements in the replaced buffer.
  239. // Warning! Do not pass an element in the list as an argument to the constructor,
  240. // because reallocating will move the data from that location before being sent to the constructor.
  241. // Post-condition: Returns an index to the new element in the list.
  242. template<typename... ARGS>
  243. intptr_t pushConstructGetIndex(ARGS&&... args) {
  244. this->impl_setLength(this->impl_length + 1);
  245. new (&(this->last())) T(std::forward<ARGS>(args)...);
  246. return this->lastIndex();
  247. }
  248. // TODO: Implement constant time remove with swap for not preserving any order.
  249. // Side-effect: Deletes the element at removedIndex without changing the order of other elements.
  250. // Pre-condition: Returns true on success and false on failure.
  251. bool remove(intptr_t removedIndex) {
  252. if (0 <= removedIndex && removedIndex < this->impl_length) {
  253. // Shift all following elements without cloning, which will call the destructor for the removed element.
  254. for (intptr_t e = removedIndex; e < this->impl_length - 1; e++) {
  255. // Move assignment.
  256. this->impl_elements[e] = std::move(this->impl_elements[e + 1]);
  257. }
  258. this->impl_length--;
  259. return true;
  260. } else {
  261. return false;
  262. }
  263. }
  264. // Side-effect: Deletes the last element.
  265. // Pre-condition: Returns true on success and false on failure.
  266. bool pop() {
  267. if (this->impl_length > 0) {
  268. //impl_elements[this->impl_length - 1].~T();
  269. this->impl_setLength(this->impl_length - 1);
  270. return true;
  271. } else {
  272. return false;
  273. }
  274. }
  275. };
  276. }
  277. #endif