diff --git a/cpp/Platform.Collections.Tests/AllTests.cpp b/cpp/Platform.Collections.Tests/AllTests.cpp index 9c2ea451..c9471d16 100644 --- a/cpp/Platform.Collections.Tests/AllTests.cpp +++ b/cpp/Platform.Collections.Tests/AllTests.cpp @@ -7,6 +7,7 @@ #include "FillersTests.cpp" #include "ListTests.cpp" #include "NodeTests.cpp" +#include "StackVectorTests.cpp" #include "StringTests.cpp" #include "WalkersTests.cpp" #include "StackTests.cpp" diff --git a/cpp/Platform.Collections.Tests/StackVectorBenchmark.cpp b/cpp/Platform.Collections.Tests/StackVectorBenchmark.cpp new file mode 100644 index 00000000..a1c432b8 --- /dev/null +++ b/cpp/Platform.Collections.Tests/StackVectorBenchmark.cpp @@ -0,0 +1,234 @@ +#include +#include +#include + +namespace Platform::Collections::Tests +{ + class PerformanceTimer + { + private: + std::chrono::high_resolution_clock::time_point start_time; + + public: + PerformanceTimer() : start_time(std::chrono::high_resolution_clock::now()) {} + + double elapsed_ms() const + { + auto end_time = std::chrono::high_resolution_clock::now(); + auto duration = std::chrono::duration_cast(end_time - start_time); + return duration.count() / 1000.0; + } + }; + + TEST(StackVectorBenchmark, PushBackPerformance) + { + const int iterations = 10000; + const int elements_per_iteration = 64; // Use stack capacity + + // Test StackVector performance + PerformanceTimer stack_timer; + for (int i = 0; i < iterations; ++i) + { + StackVector sv; + for (int j = 0; j < elements_per_iteration; ++j) + { + sv.push_back(j); + } + } + double stack_time = stack_timer.elapsed_ms(); + + // Test std::vector performance + PerformanceTimer vector_timer; + for (int i = 0; i < iterations; ++i) + { + std::vector v; + v.reserve(elements_per_iteration); + for (int j = 0; j < elements_per_iteration; ++j) + { + v.push_back(j); + } + } + double vector_time = vector_timer.elapsed_ms(); + + std::cout << "StackVector time: " << stack_time << " ms" << std::endl; + std::cout << "std::vector time: " << vector_time << " ms" << std::endl; + std::cout << "Performance ratio (lower is better for StackVector): " << stack_time / vector_time << std::endl; + + // StackVector should be at least competitive with std::vector for small sizes + ASSERT_LT(stack_time / vector_time, 2.0) << "StackVector should not be more than 2x slower than std::vector"; + } + + TEST(StackVectorBenchmark, RandomAccessPerformance) + { + const int iterations = 100000; + const int vector_size = 32; + + // Setup data + StackVector sv; + std::vector v; + + for (int i = 0; i < vector_size; ++i) + { + sv.push_back(i); + v.push_back(i); + } + + // Test StackVector random access + PerformanceTimer stack_timer; + volatile int sum = 0; // Prevent optimization + for (int i = 0; i < iterations; ++i) + { + for (int j = 0; j < vector_size; ++j) + { + sum += sv[j]; + } + } + double stack_time = stack_timer.elapsed_ms(); + + // Test std::vector random access + PerformanceTimer vector_timer; + sum = 0; + for (int i = 0; i < iterations; ++i) + { + for (int j = 0; j < vector_size; ++j) + { + sum += v[j]; + } + } + double vector_time = vector_timer.elapsed_ms(); + + std::cout << "StackVector random access time: " << stack_time << " ms" << std::endl; + std::cout << "std::vector random access time: " << vector_time << " ms" << std::endl; + std::cout << "Performance ratio (lower is better for StackVector): " << stack_time / vector_time << std::endl; + + // Random access should be very similar between both containers + ASSERT_LT(stack_time / vector_time, 1.5) << "StackVector random access should be competitive with std::vector"; + } + + TEST(StackVectorBenchmark, IterationPerformance) + { + const int iterations = 100000; + const int vector_size = 32; + + // Setup data + StackVector sv; + std::vector v; + + for (int i = 0; i < vector_size; ++i) + { + sv.push_back(i); + v.push_back(i); + } + + // Test StackVector iteration + PerformanceTimer stack_timer; + volatile int sum = 0; // Prevent optimization + for (int i = 0; i < iterations; ++i) + { + for (const auto& element : sv) + { + sum += element; + } + } + double stack_time = stack_timer.elapsed_ms(); + + // Test std::vector iteration + PerformanceTimer vector_timer; + sum = 0; + for (int i = 0; i < iterations; ++i) + { + for (const auto& element : v) + { + sum += element; + } + } + double vector_time = vector_timer.elapsed_ms(); + + std::cout << "StackVector iteration time: " << stack_time << " ms" << std::endl; + std::cout << "std::vector iteration time: " << vector_time << " ms" << std::endl; + std::cout << "Performance ratio (lower is better for StackVector): " << stack_time / vector_time << std::endl; + + // Iteration should be very similar between both containers + ASSERT_LT(stack_time / vector_time, 1.5) << "StackVector iteration should be competitive with std::vector"; + } + + TEST(StackVectorBenchmark, CopyPerformance) + { + const int iterations = 10000; + const int vector_size = 32; + + // Setup source data + StackVector source_sv; + std::vector source_v; + + for (int i = 0; i < vector_size; ++i) + { + source_sv.push_back(i); + source_v.push_back(i); + } + + // Test StackVector copy performance + PerformanceTimer stack_timer; + for (int i = 0; i < iterations; ++i) + { + StackVector copy = source_sv; + (void)copy; // Suppress unused variable warning + } + double stack_time = stack_timer.elapsed_ms(); + + // Test std::vector copy performance + PerformanceTimer vector_timer; + for (int i = 0; i < iterations; ++i) + { + std::vector copy = source_v; + (void)copy; // Suppress unused variable warning + } + double vector_time = vector_timer.elapsed_ms(); + + std::cout << "StackVector copy time: " << stack_time << " ms" << std::endl; + std::cout << "std::vector copy time: " << vector_time << " ms" << std::endl; + std::cout << "Performance ratio (lower is better for StackVector): " << stack_time / vector_time << std::endl; + + // Stack-allocated copy should be faster than heap-allocated copy + ASSERT_LT(stack_time, vector_time) << "StackVector copy should be faster than std::vector copy for small sizes"; + } + + TEST(StackVectorBenchmark, AllocationPerformance) + { + const int iterations = 50000; + const int vector_size = 16; + + // Test StackVector allocation performance + PerformanceTimer stack_timer; + for (int i = 0; i < iterations; ++i) + { + StackVector sv; + for (int j = 0; j < vector_size; ++j) + { + sv.push_back(j); + } + // Destructor called here + } + double stack_time = stack_timer.elapsed_ms(); + + // Test std::vector allocation performance + PerformanceTimer vector_timer; + for (int i = 0; i < iterations; ++i) + { + std::vector v; + for (int j = 0; j < vector_size; ++j) + { + v.push_back(j); + } + // Destructor called here + } + double vector_time = vector_timer.elapsed_ms(); + + std::cout << "StackVector allocation time: " << stack_time << " ms" << std::endl; + std::cout << "std::vector allocation time: " << vector_time << " ms" << std::endl; + std::cout << "Performance ratio (lower is better for StackVector): " << stack_time / vector_time << std::endl; + + // StackVector should be significantly faster for allocation/deallocation + ASSERT_LT(stack_time, vector_time * 0.8) << "StackVector should be faster than std::vector for allocation/deallocation"; + } +} \ No newline at end of file diff --git a/cpp/Platform.Collections.Tests/StackVectorTests.cpp b/cpp/Platform.Collections.Tests/StackVectorTests.cpp new file mode 100644 index 00000000..ffabe816 --- /dev/null +++ b/cpp/Platform.Collections.Tests/StackVectorTests.cpp @@ -0,0 +1,548 @@ +#include +#include + +namespace Platform::Collections::Tests +{ + TEST(StackVectorTests, DefaultConstruction) + { + StackVector sv; + ASSERT_TRUE(sv.empty()); + ASSERT_EQ(sv.size(), 0); + ASSERT_TRUE(sv.is_using_stack()); + ASSERT_EQ(sv.capacity(), sv.stack_capacity()); + } + + TEST(StackVectorTests, SizeConstruction) + { + StackVector sv(5); + ASSERT_FALSE(sv.empty()); + ASSERT_EQ(sv.size(), 5); + ASSERT_TRUE(sv.is_using_stack()); + + for (size_t i = 0; i < sv.size(); ++i) + { + ASSERT_EQ(sv[i], int{}); + } + } + + TEST(StackVectorTests, ValueConstruction) + { + StackVector sv(3, 42); + ASSERT_EQ(sv.size(), 3); + ASSERT_TRUE(sv.is_using_stack()); + + for (size_t i = 0; i < sv.size(); ++i) + { + ASSERT_EQ(sv[i], 42); + } + } + + TEST(StackVectorTests, InitializerListConstruction) + { + StackVector sv{1, 2, 3, 4, 5}; + ASSERT_EQ(sv.size(), 5); + ASSERT_TRUE(sv.is_using_stack()); + + for (size_t i = 0; i < sv.size(); ++i) + { + ASSERT_EQ(sv[i], static_cast(i + 1)); + } + } + + TEST(StackVectorTests, IteratorConstruction) + { + std::vector source{10, 20, 30}; + StackVector sv(source.begin(), source.end()); + ASSERT_EQ(sv.size(), 3); + ASSERT_TRUE(sv.is_using_stack()); + + ASSERT_EQ(sv[0], 10); + ASSERT_EQ(sv[1], 20); + ASSERT_EQ(sv[2], 30); + } + + TEST(StackVectorTests, CopyConstruction) + { + StackVector sv1{1, 2, 3}; + StackVector sv2(sv1); + + ASSERT_EQ(sv1.size(), sv2.size()); + ASSERT_TRUE(sv2.is_using_stack()); + + for (size_t i = 0; i < sv1.size(); ++i) + { + ASSERT_EQ(sv1[i], sv2[i]); + } + } + + TEST(StackVectorTests, MoveConstruction) + { + StackVector sv1{1, 2, 3}; + size_t original_size = sv1.size(); + + StackVector sv2(std::move(sv1)); + + ASSERT_EQ(sv2.size(), original_size); + ASSERT_EQ(sv1.size(), 0); + ASSERT_TRUE(sv2.is_using_stack()); + + ASSERT_EQ(sv2[0], 1); + ASSERT_EQ(sv2[1], 2); + ASSERT_EQ(sv2[2], 3); + } + + TEST(StackVectorTests, Assignment) + { + StackVector sv1{1, 2, 3}; + StackVector sv2; + + sv2 = sv1; + ASSERT_EQ(sv1.size(), sv2.size()); + + for (size_t i = 0; i < sv1.size(); ++i) + { + ASSERT_EQ(sv1[i], sv2[i]); + } + } + + TEST(StackVectorTests, MoveAssignment) + { + StackVector sv1{1, 2, 3}; + StackVector sv2; + size_t original_size = sv1.size(); + + sv2 = std::move(sv1); + ASSERT_EQ(sv2.size(), original_size); + ASSERT_EQ(sv1.size(), 0); + + ASSERT_EQ(sv2[0], 1); + ASSERT_EQ(sv2[1], 2); + ASSERT_EQ(sv2[2], 3); + } + + TEST(StackVectorTests, ElementAccess) + { + StackVector sv{10, 20, 30}; + + ASSERT_EQ(sv[0], 10); + ASSERT_EQ(sv[1], 20); + ASSERT_EQ(sv[2], 30); + + ASSERT_EQ(sv.at(0), 10); + ASSERT_EQ(sv.at(1), 20); + ASSERT_EQ(sv.at(2), 30); + + ASSERT_EQ(sv.front(), 10); + ASSERT_EQ(sv.back(), 30); + + ASSERT_EQ(*sv.data(), 10); + } + + TEST(StackVectorTests, ElementAccessOutOfRange) + { + StackVector sv{10, 20, 30}; + + ASSERT_THROW(sv.at(3), std::out_of_range); + ASSERT_THROW(sv.at(100), std::out_of_range); + } + + TEST(StackVectorTests, Iterators) + { + StackVector sv{1, 2, 3, 4, 5}; + + int expected = 1; + for (auto it = sv.begin(); it != sv.end(); ++it) + { + ASSERT_EQ(*it, expected++); + } + + expected = 1; + for (auto it = sv.cbegin(); it != sv.cend(); ++it) + { + ASSERT_EQ(*it, expected++); + } + + expected = 5; + for (auto it = sv.rbegin(); it != sv.rend(); ++it) + { + ASSERT_EQ(*it, expected--); + } + } + + TEST(StackVectorTests, Capacity) + { + StackVector sv; + + ASSERT_EQ(sv.capacity(), 5); + ASSERT_TRUE(sv.empty()); + ASSERT_EQ(sv.size(), 0); + ASSERT_LT(sv.max_size(), std::numeric_limits::max()); + } + + TEST(StackVectorTests, PushBack) + { + StackVector sv; + + sv.push_back(1); + ASSERT_EQ(sv.size(), 1); + ASSERT_EQ(sv[0], 1); + ASSERT_TRUE(sv.is_using_stack()); + + sv.push_back(2); + ASSERT_EQ(sv.size(), 2); + ASSERT_EQ(sv[1], 2); + + int value = 3; + sv.push_back(std::move(value)); + ASSERT_EQ(sv.size(), 3); + ASSERT_EQ(sv[2], 3); + } + + TEST(StackVectorTests, EmplaceBack) + { + StackVector sv; + + auto& ref = sv.emplace_back("hello"); + ASSERT_EQ(sv.size(), 1); + ASSERT_EQ(sv[0], "hello"); + ASSERT_EQ(&ref, &sv[0]); + + sv.emplace_back(5, 'a'); + ASSERT_EQ(sv.size(), 2); + ASSERT_EQ(sv[1], "aaaaa"); + } + + TEST(StackVectorTests, PopBack) + { + StackVector sv{1, 2, 3}; + + sv.pop_back(); + ASSERT_EQ(sv.size(), 2); + ASSERT_EQ(sv[1], 2); + + sv.pop_back(); + ASSERT_EQ(sv.size(), 1); + ASSERT_EQ(sv[0], 1); + + sv.pop_back(); + ASSERT_EQ(sv.size(), 0); + ASSERT_TRUE(sv.empty()); + + // Pop on empty should be safe (no-op) + sv.pop_back(); + ASSERT_EQ(sv.size(), 0); + } + + TEST(StackVectorTests, Insert) + { + StackVector sv{1, 3, 5}; + + auto it = sv.insert(sv.begin() + 1, 2); + ASSERT_EQ(sv.size(), 4); + ASSERT_EQ(*it, 2); + ASSERT_EQ(sv[0], 1); + ASSERT_EQ(sv[1], 2); + ASSERT_EQ(sv[2], 3); + ASSERT_EQ(sv[3], 5); + + sv.insert(sv.end(), 6); + ASSERT_EQ(sv.size(), 5); + ASSERT_EQ(sv[4], 6); + } + + TEST(StackVectorTests, InsertMultiple) + { + StackVector sv{1, 5}; + + auto it = sv.insert(sv.begin() + 1, 3, 2); + ASSERT_EQ(sv.size(), 5); + ASSERT_EQ(*it, 2); + ASSERT_EQ(sv[0], 1); + ASSERT_EQ(sv[1], 2); + ASSERT_EQ(sv[2], 2); + ASSERT_EQ(sv[3], 2); + ASSERT_EQ(sv[4], 5); + } + + TEST(StackVectorTests, InsertRange) + { + StackVector sv{1, 4}; + std::vector range{2, 3}; + + auto it = sv.insert(sv.begin() + 1, range.begin(), range.end()); + ASSERT_EQ(sv.size(), 4); + ASSERT_EQ(*it, 2); + ASSERT_EQ(sv[0], 1); + ASSERT_EQ(sv[1], 2); + ASSERT_EQ(sv[2], 3); + ASSERT_EQ(sv[3], 4); + } + + TEST(StackVectorTests, InsertInitializerList) + { + StackVector sv{1, 4}; + + auto it = sv.insert(sv.begin() + 1, {2, 3}); + ASSERT_EQ(sv.size(), 4); + ASSERT_EQ(*it, 2); + ASSERT_EQ(sv[0], 1); + ASSERT_EQ(sv[1], 2); + ASSERT_EQ(sv[2], 3); + ASSERT_EQ(sv[3], 4); + } + + TEST(StackVectorTests, Emplace) + { + StackVector sv{"hello", "world"}; + + auto it = sv.emplace(sv.begin() + 1, 3, 'x'); + ASSERT_EQ(sv.size(), 3); + ASSERT_EQ(*it, "xxx"); + ASSERT_EQ(sv[0], "hello"); + ASSERT_EQ(sv[1], "xxx"); + ASSERT_EQ(sv[2], "world"); + } + + TEST(StackVectorTests, Erase) + { + StackVector sv{1, 2, 3, 4, 5}; + + auto it = sv.erase(sv.begin() + 2); + ASSERT_EQ(sv.size(), 4); + ASSERT_EQ(*it, 4); + ASSERT_EQ(sv[0], 1); + ASSERT_EQ(sv[1], 2); + ASSERT_EQ(sv[2], 4); + ASSERT_EQ(sv[3], 5); + } + + TEST(StackVectorTests, EraseRange) + { + StackVector sv{1, 2, 3, 4, 5}; + + auto it = sv.erase(sv.begin() + 1, sv.begin() + 4); + ASSERT_EQ(sv.size(), 2); + ASSERT_EQ(*it, 5); + ASSERT_EQ(sv[0], 1); + ASSERT_EQ(sv[1], 5); + } + + TEST(StackVectorTests, Clear) + { + StackVector sv{1, 2, 3, 4, 5}; + + sv.clear(); + ASSERT_EQ(sv.size(), 0); + ASSERT_TRUE(sv.empty()); + ASSERT_TRUE(sv.is_using_stack()); + } + + TEST(StackVectorTests, Resize) + { + StackVector sv{1, 2, 3}; + + sv.resize(5); + ASSERT_EQ(sv.size(), 5); + ASSERT_EQ(sv[0], 1); + ASSERT_EQ(sv[1], 2); + ASSERT_EQ(sv[2], 3); + ASSERT_EQ(sv[3], int{}); + ASSERT_EQ(sv[4], int{}); + + sv.resize(2); + ASSERT_EQ(sv.size(), 2); + ASSERT_EQ(sv[0], 1); + ASSERT_EQ(sv[1], 2); + + sv.resize(4, 42); + ASSERT_EQ(sv.size(), 4); + ASSERT_EQ(sv[0], 1); + ASSERT_EQ(sv[1], 2); + ASSERT_EQ(sv[2], 42); + ASSERT_EQ(sv[3], 42); + } + + TEST(StackVectorTests, Reserve) + { + StackVector sv{1, 2, 3}; + + sv.reserve(10); + ASSERT_EQ(sv.size(), 3); + ASSERT_GE(sv.capacity(), 10); + ASSERT_FALSE(sv.is_using_stack()); // Should have moved to heap + + ASSERT_EQ(sv[0], 1); + ASSERT_EQ(sv[1], 2); + ASSERT_EQ(sv[2], 3); + } + + TEST(StackVectorTests, ShrinkToFit) + { + StackVector sv{1, 2, 3}; + + sv.reserve(10); + ASSERT_FALSE(sv.is_using_stack()); + + sv.shrink_to_fit(); + ASSERT_TRUE(sv.is_using_stack()); // Should have moved back to stack + ASSERT_EQ(sv.size(), 3); + ASSERT_EQ(sv[0], 1); + ASSERT_EQ(sv[1], 2); + ASSERT_EQ(sv[2], 3); + } + + TEST(StackVectorTests, StackToHeapTransition) + { + StackVector sv; + + ASSERT_TRUE(sv.is_using_stack()); + ASSERT_EQ(sv.capacity(), 3); + + sv.push_back(1); + sv.push_back(2); + sv.push_back(3); + ASSERT_TRUE(sv.is_using_stack()); + + sv.push_back(4); // This should trigger heap allocation + ASSERT_FALSE(sv.is_using_stack()); + ASSERT_GE(sv.capacity(), 4); + + ASSERT_EQ(sv.size(), 4); + ASSERT_EQ(sv[0], 1); + ASSERT_EQ(sv[1], 2); + ASSERT_EQ(sv[2], 3); + ASSERT_EQ(sv[3], 4); + } + + TEST(StackVectorTests, HeapGrowth) + { + StackVector sv; + + for (int i = 0; i < 10; ++i) + { + sv.push_back(i); + } + + ASSERT_FALSE(sv.is_using_stack()); + ASSERT_EQ(sv.size(), 10); + ASSERT_GE(sv.capacity(), 10); + + for (int i = 0; i < 10; ++i) + { + ASSERT_EQ(sv[i], i); + } + } + + TEST(StackVectorTests, Swap) + { + StackVector sv1{1, 2, 3}; + StackVector sv2{4, 5}; + + sv1.swap(sv2); + + ASSERT_EQ(sv1.size(), 2); + ASSERT_EQ(sv1[0], 4); + ASSERT_EQ(sv1[1], 5); + + ASSERT_EQ(sv2.size(), 3); + ASSERT_EQ(sv2[0], 1); + ASSERT_EQ(sv2[1], 2); + ASSERT_EQ(sv2[2], 3); + } + + TEST(StackVectorTests, ComparisonOperators) + { + StackVector sv1{1, 2, 3}; + StackVector sv2{1, 2, 3}; + StackVector sv3{1, 2, 4}; + StackVector sv4{1, 2}; + + ASSERT_TRUE(sv1 == sv2); + ASSERT_FALSE(sv1 != sv2); + + ASSERT_FALSE(sv1 == sv3); + ASSERT_TRUE(sv1 != sv3); + + ASSERT_TRUE(sv1 < sv3); + ASSERT_FALSE(sv3 < sv1); + + ASSERT_TRUE(sv4 < sv1); + ASSERT_FALSE(sv1 < sv4); + + ASSERT_TRUE(sv1 <= sv2); + ASSERT_TRUE(sv1 <= sv3); + ASSERT_FALSE(sv3 <= sv1); + + ASSERT_TRUE(sv3 > sv1); + ASSERT_FALSE(sv1 > sv3); + + ASSERT_TRUE(sv1 >= sv2); + ASSERT_TRUE(sv3 >= sv1); + ASSERT_FALSE(sv1 >= sv3); + } + + TEST(StackVectorTests, StdAlgorithms) + { + StackVector sv{5, 2, 8, 1, 9}; + + std::sort(sv.begin(), sv.end()); + + ASSERT_EQ(sv[0], 1); + ASSERT_EQ(sv[1], 2); + ASSERT_EQ(sv[2], 5); + ASSERT_EQ(sv[3], 8); + ASSERT_EQ(sv[4], 9); + + auto it = std::find(sv.begin(), sv.end(), 5); + ASSERT_NE(it, sv.end()); + ASSERT_EQ(*it, 5); + } + + TEST(StackVectorTests, RangeBasedFor) + { + StackVector sv{1, 2, 3, 4, 5}; + + int expected = 1; + for (const auto& value : sv) + { + ASSERT_EQ(value, expected++); + } + + for (auto& value : sv) + { + value *= 2; + } + + expected = 2; + for (const auto& value : sv) + { + ASSERT_EQ(value, expected); + expected += 2; + } + } + + TEST(StackVectorTests, AssignMethods) + { + StackVector sv; + + sv.assign(3, 42); + ASSERT_EQ(sv.size(), 3); + ASSERT_EQ(sv[0], 42); + ASSERT_EQ(sv[1], 42); + ASSERT_EQ(sv[2], 42); + + std::vector source{10, 20, 30, 40}; + sv.assign(source.begin(), source.end()); + ASSERT_EQ(sv.size(), 4); + ASSERT_EQ(sv[0], 10); + ASSERT_EQ(sv[1], 20); + ASSERT_EQ(sv[2], 30); + ASSERT_EQ(sv[3], 40); + + sv.assign({1, 2, 3}); + ASSERT_EQ(sv.size(), 3); + ASSERT_EQ(sv[0], 1); + ASSERT_EQ(sv[1], 2); + ASSERT_EQ(sv[2], 3); + } +} \ No newline at end of file diff --git a/cpp/Platform.Collections/Platform.Collections.h b/cpp/Platform.Collections/Platform.Collections.h index c36700f4..a54467a9 100644 --- a/cpp/Platform.Collections/Platform.Collections.h +++ b/cpp/Platform.Collections/Platform.Collections.h @@ -40,6 +40,8 @@ #include "Stacks/IStack.h" #include "Stacks/CStack.h" +#include "StackVector.h" + #include "BitStringExtensions.h" #include "StringExtensions.h" diff --git a/cpp/Platform.Collections/StackVector.h b/cpp/Platform.Collections/StackVector.h new file mode 100644 index 00000000..5a6cb79f --- /dev/null +++ b/cpp/Platform.Collections/StackVector.h @@ -0,0 +1,825 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Platform::Collections +{ + template + class StackVector + { + public: + using value_type = T; + using size_type = std::size_t; + using difference_type = std::ptrdiff_t; + using reference = T&; + using const_reference = const T&; + using pointer = T*; + using const_pointer = const T*; + using iterator = T*; + using const_iterator = const T*; + using reverse_iterator = std::reverse_iterator; + using const_reverse_iterator = std::reverse_iterator; + + private: + alignas(T) char stack_buffer_[sizeof(T) * StackCapacity]; + T* heap_buffer_; + size_type size_; + size_type capacity_; + bool using_stack_; + + T* get_buffer() noexcept + { + return using_stack_ ? reinterpret_cast(stack_buffer_) : heap_buffer_; + } + + const T* get_buffer() const noexcept + { + return using_stack_ ? reinterpret_cast(stack_buffer_) : heap_buffer_; + } + + void move_to_heap(size_type new_capacity) + { + if (using_stack_) + { + heap_buffer_ = static_cast(std::aligned_alloc(alignof(T), sizeof(T) * new_capacity)); + if (!heap_buffer_) + { + throw std::bad_alloc(); + } + + T* stack_ptr = reinterpret_cast(stack_buffer_); + for (size_type i = 0; i < size_; ++i) + { + if constexpr (std::is_move_constructible_v) + { + new (heap_buffer_ + i) T(std::move(stack_ptr[i])); + } + else + { + new (heap_buffer_ + i) T(stack_ptr[i]); + } + stack_ptr[i].~T(); + } + + using_stack_ = false; + capacity_ = new_capacity; + } + } + + void grow() + { + size_type new_capacity = capacity_ == 0 ? StackCapacity : capacity_ * 2; + + if (using_stack_ && new_capacity > StackCapacity) + { + move_to_heap(new_capacity); + } + else if (!using_stack_) + { + T* new_buffer = static_cast(std::aligned_alloc(alignof(T), sizeof(T) * new_capacity)); + if (!new_buffer) + { + throw std::bad_alloc(); + } + + for (size_type i = 0; i < size_; ++i) + { + if constexpr (std::is_move_constructible_v) + { + new (new_buffer + i) T(std::move(heap_buffer_[i])); + } + else + { + new (new_buffer + i) T(heap_buffer_[i]); + } + heap_buffer_[i].~T(); + } + + std::free(heap_buffer_); + heap_buffer_ = new_buffer; + capacity_ = new_capacity; + } + } + + void destroy_all() + { + T* buffer = get_buffer(); + for (size_type i = 0; i < size_; ++i) + { + buffer[i].~T(); + } + } + + public: + StackVector() noexcept + : heap_buffer_(nullptr), size_(0), capacity_(StackCapacity), using_stack_(true) + { + } + + explicit StackVector(size_type count) + : heap_buffer_(nullptr), size_(0), capacity_(StackCapacity), using_stack_(true) + { + resize(count); + } + + StackVector(size_type count, const T& value) + : heap_buffer_(nullptr), size_(0), capacity_(StackCapacity), using_stack_(true) + { + assign(count, value); + } + + template + StackVector(InputIt first, InputIt last) + : heap_buffer_(nullptr), size_(0), capacity_(StackCapacity), using_stack_(true) + { + assign(first, last); + } + + StackVector(std::initializer_list init) + : heap_buffer_(nullptr), size_(0), capacity_(StackCapacity), using_stack_(true) + { + assign(init); + } + + StackVector(const StackVector& other) + : heap_buffer_(nullptr), size_(0), capacity_(StackCapacity), using_stack_(true) + { + assign(other.begin(), other.end()); + } + + StackVector(StackVector&& other) noexcept + : heap_buffer_(other.heap_buffer_), size_(other.size_), capacity_(other.capacity_), using_stack_(other.using_stack_) + { + if (other.using_stack_) + { + T* other_buffer = reinterpret_cast(other.stack_buffer_); + T* this_buffer = reinterpret_cast(stack_buffer_); + + for (size_type i = 0; i < size_; ++i) + { + new (this_buffer + i) T(std::move(other_buffer[i])); + other_buffer[i].~T(); + } + } + + other.heap_buffer_ = nullptr; + other.size_ = 0; + other.capacity_ = StackCapacity; + other.using_stack_ = true; + } + + ~StackVector() + { + destroy_all(); + if (!using_stack_ && heap_buffer_) + { + std::free(heap_buffer_); + } + } + + StackVector& operator=(const StackVector& other) + { + if (this != &other) + { + clear(); + assign(other.begin(), other.end()); + } + return *this; + } + + StackVector& operator=(StackVector&& other) noexcept + { + if (this != &other) + { + destroy_all(); + if (!using_stack_ && heap_buffer_) + { + std::free(heap_buffer_); + } + + heap_buffer_ = other.heap_buffer_; + size_ = other.size_; + capacity_ = other.capacity_; + using_stack_ = other.using_stack_; + + if (other.using_stack_) + { + T* other_buffer = reinterpret_cast(other.stack_buffer_); + T* this_buffer = reinterpret_cast(stack_buffer_); + + for (size_type i = 0; i < size_; ++i) + { + new (this_buffer + i) T(std::move(other_buffer[i])); + other_buffer[i].~T(); + } + } + + other.heap_buffer_ = nullptr; + other.size_ = 0; + other.capacity_ = StackCapacity; + other.using_stack_ = true; + } + return *this; + } + + StackVector& operator=(std::initializer_list ilist) + { + assign(ilist); + return *this; + } + + void assign(size_type count, const T& value) + { + clear(); + reserve(count); + for (size_type i = 0; i < count; ++i) + { + push_back(value); + } + } + + template + void assign(InputIt first, InputIt last) + { + clear(); + for (auto it = first; it != last; ++it) + { + push_back(*it); + } + } + + void assign(std::initializer_list ilist) + { + assign(ilist.begin(), ilist.end()); + } + + reference at(size_type pos) + { + if (pos >= size_) + { + throw std::out_of_range("StackVector::at: index out of range"); + } + return get_buffer()[pos]; + } + + const_reference at(size_type pos) const + { + if (pos >= size_) + { + throw std::out_of_range("StackVector::at: index out of range"); + } + return get_buffer()[pos]; + } + + reference operator[](size_type pos) noexcept + { + return get_buffer()[pos]; + } + + const_reference operator[](size_type pos) const noexcept + { + return get_buffer()[pos]; + } + + reference front() noexcept + { + return get_buffer()[0]; + } + + const_reference front() const noexcept + { + return get_buffer()[0]; + } + + reference back() noexcept + { + return get_buffer()[size_ - 1]; + } + + const_reference back() const noexcept + { + return get_buffer()[size_ - 1]; + } + + T* data() noexcept + { + return get_buffer(); + } + + const T* data() const noexcept + { + return get_buffer(); + } + + iterator begin() noexcept + { + return get_buffer(); + } + + const_iterator begin() const noexcept + { + return get_buffer(); + } + + const_iterator cbegin() const noexcept + { + return get_buffer(); + } + + iterator end() noexcept + { + return get_buffer() + size_; + } + + const_iterator end() const noexcept + { + return get_buffer() + size_; + } + + const_iterator cend() const noexcept + { + return get_buffer() + size_; + } + + reverse_iterator rbegin() noexcept + { + return reverse_iterator(end()); + } + + const_reverse_iterator rbegin() const noexcept + { + return const_reverse_iterator(end()); + } + + const_reverse_iterator crbegin() const noexcept + { + return const_reverse_iterator(end()); + } + + reverse_iterator rend() noexcept + { + return reverse_iterator(begin()); + } + + const_reverse_iterator rend() const noexcept + { + return const_reverse_iterator(begin()); + } + + const_reverse_iterator crend() const noexcept + { + return const_reverse_iterator(begin()); + } + + bool empty() const noexcept + { + return size_ == 0; + } + + size_type size() const noexcept + { + return size_; + } + + size_type max_size() const noexcept + { + return std::numeric_limits::max() / sizeof(T); + } + + void reserve(size_type new_cap) + { + if (new_cap > capacity_) + { + if (using_stack_ && new_cap > StackCapacity) + { + move_to_heap(new_cap); + } + else if (!using_stack_) + { + T* new_buffer = static_cast(std::aligned_alloc(alignof(T), sizeof(T) * new_cap)); + if (!new_buffer) + { + throw std::bad_alloc(); + } + + for (size_type i = 0; i < size_; ++i) + { + if constexpr (std::is_move_constructible_v) + { + new (new_buffer + i) T(std::move(heap_buffer_[i])); + } + else + { + new (new_buffer + i) T(heap_buffer_[i]); + } + heap_buffer_[i].~T(); + } + + std::free(heap_buffer_); + heap_buffer_ = new_buffer; + capacity_ = new_cap; + } + } + } + + size_type capacity() const noexcept + { + return capacity_; + } + + void shrink_to_fit() + { + if (!using_stack_ && size_ <= StackCapacity) + { + T* buffer = get_buffer(); + T* stack_ptr = reinterpret_cast(stack_buffer_); + + for (size_type i = 0; i < size_; ++i) + { + if constexpr (std::is_move_constructible_v) + { + new (stack_ptr + i) T(std::move(buffer[i])); + } + else + { + new (stack_ptr + i) T(buffer[i]); + } + buffer[i].~T(); + } + + std::free(heap_buffer_); + heap_buffer_ = nullptr; + using_stack_ = true; + capacity_ = StackCapacity; + } + } + + void clear() noexcept + { + destroy_all(); + size_ = 0; + } + + iterator insert(const_iterator pos, const T& value) + { + return insert(pos, 1, value); + } + + iterator insert(const_iterator pos, T&& value) + { + size_type index = pos - begin(); + if (size_ == capacity_) + { + grow(); + } + + T* buffer = get_buffer(); + for (size_type i = size_; i > index; --i) + { + if constexpr (std::is_move_constructible_v) + { + new (buffer + i) T(std::move(buffer[i - 1])); + } + else + { + new (buffer + i) T(buffer[i - 1]); + } + buffer[i - 1].~T(); + } + + new (buffer + index) T(std::move(value)); + ++size_; + return buffer + index; + } + + iterator insert(const_iterator pos, size_type count, const T& value) + { + size_type index = pos - begin(); + if (size_ + count > capacity_) + { + reserve(size_ + count); + } + + T* buffer = get_buffer(); + for (size_type i = size_ + count - 1; i >= index + count; --i) + { + if constexpr (std::is_move_constructible_v) + { + new (buffer + i) T(std::move(buffer[i - count])); + } + else + { + new (buffer + i) T(buffer[i - count]); + } + buffer[i - count].~T(); + } + + for (size_type i = 0; i < count; ++i) + { + new (buffer + index + i) T(value); + } + + size_ += count; + return buffer + index; + } + + template + iterator insert(const_iterator pos, InputIt first, InputIt last) + { + size_type index = pos - begin(); + size_type count = std::distance(first, last); + + if (size_ + count > capacity_) + { + reserve(size_ + count); + } + + T* buffer = get_buffer(); + for (size_type i = size_ + count - 1; i >= index + count; --i) + { + if constexpr (std::is_move_constructible_v) + { + new (buffer + i) T(std::move(buffer[i - count])); + } + else + { + new (buffer + i) T(buffer[i - count]); + } + buffer[i - count].~T(); + } + + size_type i = index; + for (auto it = first; it != last; ++it, ++i) + { + new (buffer + i) T(*it); + } + + size_ += count; + return buffer + index; + } + + iterator insert(const_iterator pos, std::initializer_list ilist) + { + return insert(pos, ilist.begin(), ilist.end()); + } + + template + iterator emplace(const_iterator pos, Args&&... args) + { + size_type index = pos - begin(); + if (size_ == capacity_) + { + grow(); + } + + T* buffer = get_buffer(); + for (size_type i = size_; i > index; --i) + { + if constexpr (std::is_move_constructible_v) + { + new (buffer + i) T(std::move(buffer[i - 1])); + } + else + { + new (buffer + i) T(buffer[i - 1]); + } + buffer[i - 1].~T(); + } + + new (buffer + index) T(std::forward(args)...); + ++size_; + return buffer + index; + } + + iterator erase(const_iterator pos) + { + size_type index = pos - begin(); + T* buffer = get_buffer(); + + buffer[index].~T(); + for (size_type i = index; i < size_ - 1; ++i) + { + if constexpr (std::is_move_constructible_v) + { + new (buffer + i) T(std::move(buffer[i + 1])); + } + else + { + new (buffer + i) T(buffer[i + 1]); + } + buffer[i + 1].~T(); + } + + --size_; + return buffer + index; + } + + iterator erase(const_iterator first, const_iterator last) + { + size_type first_index = first - begin(); + size_type last_index = last - begin(); + size_type count = last_index - first_index; + + T* buffer = get_buffer(); + for (size_type i = first_index; i < last_index; ++i) + { + buffer[i].~T(); + } + + for (size_type i = last_index; i < size_; ++i) + { + if constexpr (std::is_move_constructible_v) + { + new (buffer + i - count) T(std::move(buffer[i])); + } + else + { + new (buffer + i - count) T(buffer[i]); + } + buffer[i].~T(); + } + + size_ -= count; + return buffer + first_index; + } + + void push_back(const T& value) + { + if (size_ == capacity_) + { + grow(); + } + new (get_buffer() + size_) T(value); + ++size_; + } + + void push_back(T&& value) + { + if (size_ == capacity_) + { + grow(); + } + new (get_buffer() + size_) T(std::move(value)); + ++size_; + } + + template + reference emplace_back(Args&&... args) + { + if (size_ == capacity_) + { + grow(); + } + T* ptr = get_buffer() + size_; + new (ptr) T(std::forward(args)...); + ++size_; + return *ptr; + } + + void pop_back() + { + if (size_ > 0) + { + --size_; + get_buffer()[size_].~T(); + } + } + + void resize(size_type count) + { + if (count > size_) + { + reserve(count); + T* buffer = get_buffer(); + for (size_type i = size_; i < count; ++i) + { + new (buffer + i) T(); + } + } + else if (count < size_) + { + T* buffer = get_buffer(); + for (size_type i = count; i < size_; ++i) + { + buffer[i].~T(); + } + } + size_ = count; + } + + void resize(size_type count, const T& value) + { + if (count > size_) + { + reserve(count); + T* buffer = get_buffer(); + for (size_type i = size_; i < count; ++i) + { + new (buffer + i) T(value); + } + } + else if (count < size_) + { + T* buffer = get_buffer(); + for (size_type i = count; i < size_; ++i) + { + buffer[i].~T(); + } + } + size_ = count; + } + + void swap(StackVector& other) noexcept + { + if (using_stack_ && other.using_stack_) + { + char temp_buffer[sizeof(T) * StackCapacity]; + memcpy(temp_buffer, stack_buffer_, sizeof(T) * size_); + memcpy(stack_buffer_, other.stack_buffer_, sizeof(T) * other.size_); + memcpy(other.stack_buffer_, temp_buffer, sizeof(T) * size_); + } + else + { + std::swap(heap_buffer_, other.heap_buffer_); + if (using_stack_ != other.using_stack_) + { + if (using_stack_) + { + memcpy(other.stack_buffer_, stack_buffer_, sizeof(T) * size_); + } + else + { + memcpy(stack_buffer_, other.stack_buffer_, sizeof(T) * other.size_); + } + } + } + + std::swap(size_, other.size_); + std::swap(capacity_, other.capacity_); + std::swap(using_stack_, other.using_stack_); + } + + bool is_using_stack() const noexcept + { + return using_stack_; + } + + static constexpr size_type stack_capacity() noexcept + { + return StackCapacity; + } + }; + + template + bool operator==(const StackVector& lhs, const StackVector& rhs) + { + if (lhs.size() != rhs.size()) + return false; + + return std::equal(lhs.begin(), lhs.end(), rhs.begin()); + } + + template + bool operator!=(const StackVector& lhs, const StackVector& rhs) + { + return !(lhs == rhs); + } + + template + bool operator<(const StackVector& lhs, const StackVector& rhs) + { + return std::lexicographical_compare(lhs.begin(), lhs.end(), rhs.begin(), rhs.end()); + } + + template + bool operator<=(const StackVector& lhs, const StackVector& rhs) + { + return !(rhs < lhs); + } + + template + bool operator>(const StackVector& lhs, const StackVector& rhs) + { + return rhs < lhs; + } + + template + bool operator>=(const StackVector& lhs, const StackVector& rhs) + { + return !(lhs < rhs); + } + + template + void swap(StackVector& lhs, StackVector& rhs) noexcept + { + lhs.swap(rhs); + } +} \ No newline at end of file