| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286 |
-
- // zlib open source license
- //
- // Copyright (c) 2018 to 2025 David Forsgren Piuva
- //
- // This software is provided 'as-is', without any express or implied
- // warranty. In no event will the authors be held liable for any damages
- // arising from the use of this software.
- //
- // Permission is granted to anyone to use this software for any purpose,
- // including commercial applications, and to alter it and redistribute it
- // freely, subject to the following restrictions:
- //
- // 1. The origin of this software must not be misrepresented; you must not
- // claim that you wrote the original software. If you use this software
- // in a product, an acknowledgment in the product documentation would be
- // appreciated but is not required.
- //
- // 2. Altered source versions must be plainly marked as such, and must not be
- // misrepresented as being the original software.
- //
- // 3. This notice may not be removed or altered from any source
- // distribution.
- #ifndef DFPSR_COLLECTION_LIST
- #define DFPSR_COLLECTION_LIST
- #include "collections.h"
- #include "../base/Handle.h"
- #include "../base/DsrTraits.h"
- #include <cstdint>
- #include <utility>
- namespace dsr {
- // TODO:
- // * Allow storing names of lists in debug mode for better error messages, using the same setName method as used in Handle.
- // * Allow getting a SafePointer to all elements for faster loops without bound checks in release mode.
- // Because elements are returned by reference, the list can not know when an element is modified.
- // Therefore it must clone the entire content when assigned by value for consistent behavior during reallocation.
- // When cloning on assignment, const can be inherited from the outside for passing read only lists as const references.
- template <typename T>
- class List {
- private:
- T *impl_elements = nullptr;
- intptr_t impl_length = 0;
- intptr_t impl_buffer_length = 0;
- // Makes sure that there is memory available for storing at least minimumAllocatedLength elements.
- void impl_reserve(intptr_t minimumAllocatedLength) {
- if (minimumAllocatedLength > this->impl_buffer_length) {
- // Create a new memory allocation.
- UnsafeAllocation newAllocation = (heap_allocate(minimumAllocatedLength * sizeof(T), true));
- #ifdef SAFE_POINTER_CHECKS
- heap_setAllocationName(newAllocation.data, "List allocation");
- #endif
- T *newElements = (T*)(newAllocation.data);
- heap_increaseUseCount(newAllocation.header);
- // Use all available space.
- uintptr_t availableSize = heap_getAllocationSize(newAllocation.header);
- heap_setUsedSize(newAllocation.header, availableSize);
- // Move the data from the old allocation to the new allocation.
- if (std::is_move_constructible<T>::value) {
- // If T is move constructible, we do not have to clone the elements.
- for (intptr_t e = 0; e < this->impl_buffer_length; e++) {
- new (newElements + e) T(std::move(this->impl_elements[e]));
- }
- } else {
- // If T is not move constructible, we have to create a copy and then destroy the original.
- for (intptr_t e = 0; e < this->impl_buffer_length; e++) {
- new (newElements + e) T(this->impl_elements[e]);
- this->impl_elements[e].~T();
- }
- }
- // Transfer ownership to the new allocation.
- heap_decreaseUseCount(this->impl_elements);
- this->impl_elements = newElements;
- this->impl_buffer_length = availableSize / sizeof(T);
- }
- }
- void impl_setLength(intptr_t newLength) {
- if (newLength < this->impl_length) {
- for (intptr_t e = newLength; e < this->impl_length; e++) {
- // In-place descruction.
- this->impl_elements[e].~T();
- }
- } else {
- this->impl_reserve(newLength);
- }
- this->impl_length = newLength;
- }
- template<typename... ARGS>
- void impl_setElements(ARGS&&... args) {
- intptr_t elementCount = sizeof...(args);
- this->impl_reserve(elementCount);
- this->impl_length = elementCount;
- std::initializer_list<T> otherArguments = { T(args)... };
- for (intptr_t e = 0; e < elementCount; e++) {
- new (this->impl_elements + e) T(otherArguments.begin()[e]);
- }
- }
- public:
- // Copy constructor
- List(const List<T>& source) {
- this->impl_setLength(source.length());
- for (intptr_t e = 0; e < source.length(); e++) {
- new (this->impl_elements + e) T(source.impl_elements[e]);
- }
- }
- // Move constructor
- List(List<T>&& source) noexcept {
- // No pre-existing content when move constructing.
- this->impl_elements = source.impl_elements;
- this->impl_length = source.impl_length;
- this->impl_buffer_length = source.impl_buffer_length;
- source.impl_elements = nullptr;
- source.impl_length = 0;
- source.impl_buffer_length = 0;
- }
- // Copy assignment operator
- List& operator = (const List<T>& source) {
- if (this != &source) {
- // Delete any pre-existing content.
- this->impl_setLength(0);
- // Clone the content.
- this->impl_setLength(source.length());
- for (intptr_t e = 0; e < source.length(); e++) {
- new (this->impl_elements + e) T(source.impl_elements[e]);
- }
- }
- return *this;
- }
- // Move assignment operator
- List& operator = (List<T>&& source) {
- if (this != &source) {
- // Delete any pre-existing content when move assigning.
- this->impl_setLength(0);
- heap_decreaseUseCount(this->impl_elements);
- // Move the content.
- this->impl_elements = source.impl_elements;
- this->impl_length = source.impl_length;
- this->impl_buffer_length = source.impl_buffer_length;
- source.impl_elements = nullptr;
- source.impl_length = 0;
- source.impl_buffer_length = 0;
- }
- return *this;
- }
- // Constructors
- List() {}
- template<
- typename FIRST,
- typename... OTHERS,
- DSR_ENABLE_IF(DSR_CONVERTIBLE_TO(FIRST, T))
- >
- List(FIRST first, OTHERS... others) {
- this->impl_setElements(first, others...);
- }
- ~List() {
- // Destroy all elements.
- this->impl_setLength(0);
- // Free the allocation.
- heap_decreaseUseCount(this->impl_elements);
- this->impl_elements = nullptr;
- this->impl_buffer_length = 0;
- }
- // Post-condition: Returns the element at index from the range 0..length-1.
- T& operator[] (intptr_t index) {
- impl_baseZeroBoundCheck(index, this->impl_length, "List index");
- return this->impl_elements[index];
- }
- const T& operator[] (intptr_t index) const {
- impl_baseZeroBoundCheck(index, this->impl_length, "List index");
- return this->impl_elements[index];
- }
- // Post-condition: Returns the number of elements in the array list.
- inline intptr_t length() const {
- return this->impl_length;
- }
- // Side-effect: Makes sure that the buffer have room for at least minimumLength elements.
- // Warning! Reallocation may invalidate old pointers and references to elements in the replaced buffer.
- void reserve(intptr_t minimumLength) {
- impl_reserve(minimumLength);
- }
- // Post-condition: Returns an index to the first element, which is always zero.
- // Can be used for improving readability when used together with lastIndex.
- intptr_t firstIndex() const { return 0; }
- // Post-condition: Returns an index to the last element.
- intptr_t lastIndex() const { return this->impl_length - 1; }
- // Post-condition: Returns a reference to the first element.
- T& first() {
- impl_nonZeroLengthCheck(this->impl_length, "Length");
- return this->impl_elements[0];
- }
- // Post-condition: Returns a reference to the first element.
- const T& first() const {
- impl_nonZeroLengthCheck(this->impl_length, "Length");
- return this->impl_elements[0];
- }
- // Post-condition: Returns a reference to the last element.
- T& last() {
- impl_nonZeroLengthCheck(this->impl_length, "Length");
- return this->impl_elements[this->lastIndex()];
- }
- // Post-condition: Returns a reference to the last element.
- const T& last() const {
- impl_nonZeroLengthCheck(this->impl_length, "Length");
- return this->impl_elements[this->lastIndex()];
- }
- // Side-effect: Removes all elements by setting the count to zero.
- void clear() {
- this->impl_setLength(0);
- }
- // Side-effect: Swap the order of two elements.
- // Useful for moving and sorting elements.
- void swap(intptr_t indexA, intptr_t indexB) {
- impl_baseZeroBoundCheck(indexA, this->impl_length, "Swap index A");
- impl_baseZeroBoundCheck(indexB, this->impl_length, "Swap index B");
- std::swap(this->impl_elements[indexA], this->impl_elements[indexB]);
- }
- T& push(const T& newValue) {
- this->impl_setLength(this->impl_length + 1);
- // Copy construction.
- new (&(this->last())) T(newValue);
- return this->last();
- }
- // Side-effect: Pushes a new element at the end.
- // Warning! Reallocation may invalidate old pointers and references to elements in the replaced buffer.
- // Post-condition: Returns an index to the new element in the list.
- intptr_t pushGetIndex(const T& newValue) {
- this->impl_setLength(this->impl_length + 1);
- // Copy construction.
- new (&(this->last())) T(newValue);
- return this->lastIndex();
- }
- // Side-effect: Pushes a new element constructed using the given arguments.
- // Warning! Reallocation may invalidate old pointers and references to elements in the replaced buffer.
- // Post-condition: Returns a reference to the new element in the list.
- template<typename... ARGS>
- T& pushConstruct(ARGS&&... args) {
- this->impl_setLength(this->impl_length + 1);
- // Copy construction.
- new (&(this->last())) T(std::forward<ARGS>(args)...);
- return this->last();
- }
- // Side-effect: Pushes a new element constructed using the given arguments.
- // Warning! Reallocation may invalidate old pointers and references to elements in the replaced buffer.
- // Post-condition: Returns an index to the new element in the list.
- template<typename... ARGS>
- intptr_t pushConstructGetIndex(ARGS&&... args) {
- this->impl_setLength(this->impl_length + 1);
- new (&(this->last())) T(std::forward<ARGS>(args)...);
- return this->lastIndex();
- }
- // TODO: Implement constant time remove with swap for not preserving any order.
- // Side-effect: Deletes the element at removedIndex without changing the order of other elements.
- // Pre-condition: Returns true on success and false on failure.
- bool remove(intptr_t removedIndex) {
- if (0 <= removedIndex && removedIndex < this->impl_length) {
- // Shift all following elements without cloning, which will call the destructor for the removed element.
- for (intptr_t e = removedIndex; e < this->impl_length - 1; e++) {
- // Move assignment.
- this->impl_elements[e] = std::move(this->impl_elements[e + 1]);
- }
- this->impl_length--;
- return true;
- } else {
- return false;
- }
- }
- // Side-effect: Deletes the last element.
- // Pre-condition: Returns true on success and false on failure.
- bool pop() {
- if (this->impl_length > 0) {
- //impl_elements[this->impl_length - 1].~T();
- this->impl_setLength(this->impl_length - 1);
- return true;
- } else {
- return false;
- }
- }
- };
- }
- #endif
|