From 86299ca2907565e09cb10c2ddd3661ad1ceb6cb0 Mon Sep 17 00:00:00 2001 From: Lin Zhihao <59785146+LinZhihao-723@users.noreply.github.com> Date: Sun, 18 Aug 2024 00:25:47 -0400 Subject: [PATCH] core: Add `Array` template class to abstract a fixed-size contiguous buffer whose size is determined at runtime. (#513) --- components/core/CMakeLists.txt | 2 + components/core/src/clp/Array.hpp | 111 +++++++++++++++++++++++++++ components/core/tests/test-Array.cpp | 57 ++++++++++++++ 3 files changed, 170 insertions(+) create mode 100644 components/core/src/clp/Array.hpp create mode 100644 components/core/tests/test-Array.cpp diff --git a/components/core/CMakeLists.txt b/components/core/CMakeLists.txt index dba237ed1..c1a889de8 100644 --- a/components/core/CMakeLists.txt +++ b/components/core/CMakeLists.txt @@ -269,6 +269,7 @@ set(SOURCE_FILES_clp_s_unitTest ) set(SOURCE_FILES_unitTest + src/clp/Array.hpp src/clp/BufferedFileReader.cpp src/clp/BufferedFileReader.hpp src/clp/BufferReader.cpp @@ -474,6 +475,7 @@ set(SOURCE_FILES_unitTest submodules/sqlite3/sqlite3.h submodules/sqlite3/sqlite3ext.h tests/LogSuppressor.hpp + tests/test-Array.cpp tests/test-BufferedFileReader.cpp tests/test-EncodedVariableInterpreter.cpp tests/test-encoding_methods.cpp diff --git a/components/core/src/clp/Array.hpp b/components/core/src/clp/Array.hpp new file mode 100644 index 000000000..f94892493 --- /dev/null +++ b/components/core/src/clp/Array.hpp @@ -0,0 +1,111 @@ +#ifndef CLP_ARRAY_HPP +#define CLP_ARRAY_HPP + +#include +#include +#include +#include +#include +#include + +namespace clp { +/** + * Class for a runtime fix-sized array. + * @tparam T The type of elements in the array. The type must be default initializable so that this + * class doesn't need to implement a constructor which takes an initializer list. + */ +template +requires(std::is_fundamental_v || std::default_initializable) +class Array { +public: + // Types + using Iterator = T*; + using ConstIterator = T const*; + + // Constructors + // NOLINTNEXTLINE(*-avoid-c-arrays) + explicit Array(size_t size) : m_data{std::make_unique(size)}, m_size{size} { + if constexpr (std::is_fundamental_v) { + memset(m_data.get(), 0, m_size * sizeof(T)); + } + } + + // Disable copy constructor and assignment operator + Array(Array const&) = delete; + auto operator=(Array const&) -> Array& = delete; + + // Default move constructor and assignment operator + Array(Array&&) = default; + auto operator=(Array&&) -> Array& = default; + + // Destructor + ~Array() = default; + + // Methods + /** + * @return Whether the array is empty. + */ + [[nodiscard]] auto empty() const -> bool { return 0 == size(); } + + /** + * @return The size of the array. + */ + [[nodiscard]] auto size() const -> size_t { return m_size; } + + /** + * @return A pointer to the underlying data buffer. + */ + [[nodiscard]] auto data() -> T* { return m_data.get(); } + + /** + * @return A pointer to the underlying data buffer. + */ + [[nodiscard]] auto data() const -> T const* { return m_data.get(); } + + /** + * @param idx + * @return The element at the given index. + * @throw `OperationFailed` if the given index is out of bound. + */ + [[nodiscard]] auto at(size_t idx) -> T& { + assert_is_in_range(idx); + return m_data[idx]; + } + + /** + * @param idx + * @return The element at the given index. + * @throw `OperationFailed` if the given index is out of bound. + */ + [[nodiscard]] auto at(size_t idx) const -> T const& { + assert_is_in_range(idx); + return m_data[idx]; + } + + [[nodiscard]] auto begin() -> Iterator { return m_data.get(); } + + [[nodiscard]] auto end() -> Iterator { return m_data.get() + m_size; } + + [[nodiscard]] auto begin() const -> ConstIterator { return m_data.get(); } + + [[nodiscard]] auto end() const -> ConstIterator { return m_data.get() + m_size; } + +private: + /** + * @param idx + * @throw `std::out_of_range` if the given index is out of bound. + */ + auto assert_is_in_range(size_t idx) -> void { + if (idx >= m_size) { + throw std::out_of_range("clp::Array out-of-range access."); + } + } + + // Variables + // NOLINTNEXTLINE(*-avoid-c-arrays) + std::unique_ptr m_data; + size_t m_size; +}; +} // namespace clp + +#endif // CLP_ARRAY_HPP diff --git a/components/core/tests/test-Array.cpp b/components/core/tests/test-Array.cpp new file mode 100644 index 000000000..20b68fbd0 --- /dev/null +++ b/components/core/tests/test-Array.cpp @@ -0,0 +1,57 @@ +#include +#include +#include +#include + +#include + +#include "../src/clp/Array.hpp" + +using clp::Array; +using std::vector; + +// NOLINTNEXTLINE(readability-function-cognitive-complexity) +TEST_CASE("array_fundamental", "[clp::Array]") { + Array clp_array_empty{0}; + REQUIRE(clp_array_empty.empty()); + // NOLINTNEXTLINE(readability-container-size-empty) + REQUIRE((0 == clp_array_empty.size())); + REQUIRE((clp_array_empty.begin() == clp_array_empty.end())); + + constexpr size_t cBufferSize{1024}; + + vector std_vector; + for (int i{0}; i < cBufferSize; ++i) { + std_vector.push_back(i); + } + + Array clp_array{cBufferSize}; + auto const& clp_array_const_ref = clp_array; + std::for_each(clp_array_const_ref.begin(), clp_array_const_ref.end(), [](int i) -> void { + REQUIRE((0 == i)); + }); + + std::copy(std_vector.cbegin(), std_vector.cend(), clp_array.begin()); + + size_t idx{0}; + for (auto const val : clp_array) { + REQUIRE((val == clp_array.at(idx))); + REQUIRE((val == std_vector.at(idx))); + ++idx; + } + REQUIRE((cBufferSize == idx)); + REQUIRE_THROWS(clp_array.at(idx)); +} + +TEST_CASE("array_default_initializable", "[clp::Array]") { + Array clp_array_empty{0}; + REQUIRE(clp_array_empty.empty()); + // NOLINTNEXTLINE(readability-container-size-empty) + REQUIRE((0 == clp_array_empty.size())); + REQUIRE((clp_array_empty.begin() == clp_array_empty.end())); + + vector const std_vector{"yscope", "clp", "clp::Array", "default_initializable"}; + Array clp_array{std_vector.size()}; + std::copy(std_vector.cbegin(), std_vector.cend(), clp_array.begin()); + REQUIRE(std::equal(std_vector.begin(), std_vector.end(), clp_array.begin(), clp_array.end())); +}