diff --git a/CMakeLists.txt b/CMakeLists.txt index 19e29ae2e..b0207b12d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -37,7 +37,8 @@ OPTION(BUILD_TESTS "xparrow test suite" OFF) # ===== set(XPARROW_HEADERS - ${XPARROW_INCLUDE_DIR}/xparrow/xparrow_version.pp + ${XPARROW_INCLUDE_DIR}/xparrow/xbuffer.hpp + ${XPARROW_INCLUDE_DIR}/xparrow/xparrow_version.hpp ) add_library(xparrow INTERFACE) @@ -65,7 +66,7 @@ install(TARGETS xparrow export(EXPORT ${PROJECT_NAME}-targets FILE "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Targets.cmake") -install(FILES ${Xparrow_HEADERS} + install(FILES ${XPARROW_HEADERS} DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/xparrow) set(XPARROW_CMAKECONFIG_INSTALL_DIR "${CMAKE_INSTALL_DATADIR}/cmake/${PROJECT_NAME}" CACHE diff --git a/include/xparrow/xbuffer.hpp b/include/xparrow/xbuffer.hpp new file mode 100644 index 000000000..8f14f7161 --- /dev/null +++ b/include/xparrow/xbuffer.hpp @@ -0,0 +1,251 @@ +/*************************************************************************** +* Copyright (c) QuantStack * +* * +* Distributed under the terms of the BSD 3-Clause License. * +* * +* The full license is in the file LICENSE, distributed with this software. * +***************************************************************************/ + +#pragma once + +#include +#include +#include + +namespace xparrow +{ + namespace impl + { + template + struct xbuffer_data + { + using value_type = T; + using pointer = T*; + using size_type = std::size_t; + + bool empty() const noexcept; + size_type size() const noexcept; + + template + U* data() noexcept; + + template + const U* data() const noexcept; + + void swap(xbuffer_data& rhs) noexcept; + bool equal(const xbuffer_data& rhs) const; + + pointer p_data = nullptr; + size_type m_size = 0; + }; + } + + /** + * @class xbuffer + * @brief Object that owns a piece of contiguous memory + */ + template + class xbuffer : private impl::xbuffer_data + { + public: + + using base_type = impl::xbuffer_data; + using value_type = typename base_type::value_type; + using pointer = typename base_type::pointer; + using size_type = typename base_type::size_type; + + xbuffer() = default; + explicit xbuffer(size_type size); + xbuffer(pointer data, size_type size); + + ~xbuffer(); + + xbuffer(const xbuffer&); + xbuffer& operator=(const xbuffer&); + + xbuffer(xbuffer&&); + xbuffer& operator=(xbuffer&&); + + using base_type::empty; + using base_type::size; + using base_type::data; + + void resize(size_type new_size); + + void swap(xbuffer&) noexcept; + bool equal(const xbuffer& rhs) const; + + private: + + pointer allocate(size_type size) const; + void deallocate(pointer mem) const; + }; + + template + bool operator==(const xbuffer& lhs, const xbuffer& rhs); + + /** + * @class xbuffer_reference + * @brief Object that wraps a piece of contiguous memory + * but does not own it. + */ + /*class xbuffer_reference : private xbuffer_data + { + public: + + using base_type = xbuffer_data; + + xbuffer_reference(uint8_t* data, std::size_t size); + + using base_type::size; + using base_type::data; + + void swap(xbuffer_reference&) noexcept; + };*/ + + /****************************** + * xbuffer_data implementation * + ******************************/ + + namespace impl + { + template + bool xbuffer_data::empty() const noexcept + { + return size() == size_type(0); + } + + template + auto xbuffer_data::size() const noexcept -> size_type + { + return m_size; + } + + template + template + U* xbuffer_data::data() noexcept + { + return reinterpret_cast(p_data); + } + + template + template + const U* xbuffer_data::data() const noexcept + { + return reinterpret_cast(p_data); + } + + template + void xbuffer_data::swap(xbuffer_data& rhs) noexcept + { + std::swap(p_data, rhs.p_data); + std::swap(m_size, rhs.m_size); + } + + template + bool xbuffer_data::equal(const xbuffer_data& rhs) const + { + return m_size == rhs.m_size && std::equal(p_data, p_data + m_size, rhs.p_data); + } + } + + /************************* + * xbuffer implementation * + *************************/ + + template + xbuffer::xbuffer(size_type size) + : base_type{allocate(size), size} + { + } + + template + xbuffer::xbuffer(pointer data, size_type size) + : base_type{data, size} + { + } + + template + xbuffer::~xbuffer() + { + deallocate(this->p_data); + this->p_data = nullptr; + this->m_size = 0u; + } + + template + xbuffer::xbuffer(const xbuffer& rhs) + : base_type{allocate(rhs.m_size), rhs.size()} + { + std::copy(rhs.data(), rhs.data() + rhs.size(), data()); + } + + template + xbuffer& xbuffer::operator=(const xbuffer& rhs) + { + if (this != &rhs) + { + xbuffer tmp(rhs); + swap(tmp); + } + return *this; + } + + template + xbuffer::xbuffer(xbuffer&& rhs) + : base_type{rhs.data(), rhs.size()} + { + rhs.p_data = nullptr; + rhs.m_size = 0u; + } + + template + xbuffer& xbuffer::operator=(xbuffer&& rhs) + { + swap(rhs); + return *this; + } + + template + void xbuffer::resize(size_type n) + { + // TODO: add capacity, resize if growing only and define a shrink_to_fit method + if (n != size()) + { + xbuffer tmp(n); + std::copy(data(), data() + size(), tmp.data()); + swap(tmp); + } + } + + template + void xbuffer::swap(xbuffer& rhs) noexcept + { + base_type::swap(rhs); + } + + template + bool xbuffer::equal(const xbuffer& rhs) const + { + return base_type::equal(rhs); + } + + template + auto xbuffer::allocate(size_type size) const -> pointer + { + return new T[size]; + } + + template + void xbuffer::deallocate(pointer mem) const + { + delete[] mem; + } + + template + bool operator==(const xbuffer& lhs, const xbuffer& rhs) + { + return lhs.equal(rhs); + } +} + diff --git a/include/xparrow/xparrow_version.hpp b/include/xparrow/xparrow_version.hpp index 5053e451f..aad5a7b19 100644 --- a/include/xparrow/xparrow_version.hpp +++ b/include/xparrow/xparrow_version.hpp @@ -5,11 +5,10 @@ * * * The full license is in the file LICENSE, distributed with this software. * ***************************************************************************/ -#ifndef XPARROW_VERSION_HPP -#define XPARROW_VERSION_HPP + +#pragma once #define XPARROW_VERSION_MAJOR 0 #define XPARROW_VERSION_MINOR 0 #define XPARROW_VERSION_PATCH 1 -#endif diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 7f5a192f4..0fec8f880 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -29,7 +29,8 @@ endif() set(XPARROW_TESTS main.cpp + test_xbuffer.cpp ) set(test_target "test_xparrow_lib") add_executable(${test_target} ${XPARROW_TESTS}) -target_link_libraries(${test_target} PRIVATE doctest::doctest) +target_link_libraries(${test_target} PRIVATE xparrow doctest::doctest) diff --git a/test/test_xbuffer.cpp b/test/test_xbuffer.cpp new file mode 100644 index 000000000..88fc14a6e --- /dev/null +++ b/test/test_xbuffer.cpp @@ -0,0 +1,140 @@ +/*************************************************************************** +* Copyright (c) QuantStack * +* * +* Distributed under the terms of the BSD 3-Clause License. * +* * +* The full license is in the file LICENSE, distributed with this software. * +***************************************************************************/ + +#include "doctest/doctest.h" + +#include + +#include "xparrow/xbuffer.hpp" + +namespace xparrow +{ + using buffer_test_type = xbuffer; + + namespace + { + uint8_t* make_test_buffer(std::size_t size, uint8_t start_value = 0) + { + uint8_t* res = new uint8_t[size]; + std::iota(res, res + size, start_value); + return res; + } + } + + TEST_SUITE("xbuffer") + { + TEST_CASE("constructors") + { + { + [[maybe_unused]] buffer_test_type b(8u); + } + + { + const std::size_t size = 8u; + [[maybe_unused]] buffer_test_type b(make_test_buffer(size), size); + } + + buffer_test_type b0; + CHECK_EQ(b0.data(), nullptr); + CHECK_EQ(b0.size(), 0u); + + const std::size_t expected_size = 4u; + buffer_test_type b1(expected_size); + CHECK_NE(b1.data(), nullptr); + CHECK_EQ(b1.size(), expected_size); + + uint8_t* mem = make_test_buffer(expected_size); + buffer_test_type b2(mem, expected_size); + CHECK_EQ(b2.data(), mem); + CHECK_EQ(b2.size(), expected_size); + CHECK_EQ(b2.data()[2], uint8_t(2)); + } + + TEST_CASE("copy semantic") + { + const std::size_t size = 4; + buffer_test_type b1(make_test_buffer(size), size); + buffer_test_type b2(b1); + CHECK_EQ(b1, b2); + + const std::size_t size2 = 8; + buffer_test_type b3(make_test_buffer(size2, 4), size2); + b2 = b3; + CHECK_EQ(b2, b3); + CHECK_NE(b1, b2); + } + + TEST_CASE("move semantic") + { + const std::size_t size = 4; + buffer_test_type b1(make_test_buffer(size), size); + buffer_test_type control(b1); + buffer_test_type b2(std::move(b1)); + CHECK_EQ(b2, control); + CHECK_EQ(b1.size(), 0); + CHECK(b1.empty()); + CHECK_EQ(b1.data(), nullptr); + + const std::size_t size2 = 8; + buffer_test_type b4(make_test_buffer(size2, 4), size2); + buffer_test_type control2(b4); + b2 = std::move(b4); + CHECK_EQ(b2, control2); + CHECK_EQ(b4, control); + } + + TEST_CASE("data") + { + const std::size_t size = 4; + buffer_test_type b1(make_test_buffer(size), size); + + const uint8_t expected_value = 101; + const std::size_t idx = 3u; + b1.data()[idx] = expected_value; + buffer_test_type b2(b1); + CHECK_EQ(b2.data()[idx], expected_value); + + buffer_test_type b3(std::move(b1)); + CHECK_EQ(b3.data()[idx], expected_value); + } + + TEST_CASE("equality comparison") + { + const std::size_t size = 4; + buffer_test_type b1(make_test_buffer(size), size); + buffer_test_type b2(make_test_buffer(size), size); + CHECK(b1 == b2); + } + + TEST_CASE("swap") + { + const std::size_t size1 = 4u; + const std::size_t size2 = 8u; + + buffer_test_type b1(make_test_buffer(size1), size1); + buffer_test_type b2(make_test_buffer(size2), size2); + auto* data1 = b1.data(); + auto* data2 = b2.data(); + b1.swap(b2); + CHECK_EQ(b1.size(), size2); + CHECK_EQ(b1.data(), data2); + CHECK_EQ(b2.size(), size1); + CHECK_EQ(b2.data(), data1); + } + + TEST_CASE("resize") + { + const std::size_t size1 = 4u; + const std::size_t size2 = 8u; + buffer_test_type b(make_test_buffer(size1), size1); + b.resize(size2); + CHECK_EQ(b.size(), size2); + CHECK_EQ(b.data()[2], 2); + } + } +}