diff --git a/include/emio/buffer.hpp b/include/emio/buffer.hpp index d99f285..02af331 100644 --- a/include/emio/buffer.hpp +++ b/include/emio/buffer.hpp @@ -18,6 +18,9 @@ namespace emio { +/// The default cache size of buffers with an internal cache. +inline constexpr size_t default_cache_size{128}; + /** * This class provides the basic API and functionality for receiving a contiguous memory region of chars to write into. * @note Use a specific subclass for a concrete instantiation. @@ -119,9 +122,9 @@ class buffer { /** * This class fulfills the buffer API by providing an endless growing buffer. - * @tparam StorageSize The size of the inlined storage for small buffer optimization. + * @tparam StorageSize The size of the internal storage used for small buffer optimization. */ -template +template class memory_buffer final : public buffer { public: /** @@ -224,7 +227,7 @@ class span_buffer : public buffer { * @tparam StorageSize The size of the storage. */ template -class static_buffer : private std::array, public span_buffer { +class static_buffer final : private std::array, public span_buffer { public: /** * Constructs and initializes the buffer with the storage. @@ -243,8 +246,6 @@ class static_buffer : private std::array, public span_buffer namespace detail { -inline constexpr size_t internal_buffer_size{128}; - // Extracts a reference to the container from back_insert_iterator. template Container& get_container(std::back_insert_iterator it) noexcept { @@ -288,17 +289,18 @@ constexpr auto copy_str(InputIt it, InputIt end, OutputIt out) -> OutputIt { /** * This class template is used to create a buffer around different iterator types. */ -template +template class iterator_buffer; /** * This class fulfills the buffer API by using an output iterator and an internal cache. * @tparam Iterator The output iterator type. + * @tparam CacheSize The size of the internal cache. */ -template +template requires(std::input_or_output_iterator && std::output_iterator>) -class iterator_buffer final : public buffer { +class iterator_buffer final : public buffer { public: /** * Constructs and initializes the buffer with the given output iterator. @@ -347,7 +349,7 @@ class iterator_buffer final : public buffer { private: Iterator it_; - std::array cache_; + std::array cache_; }; /** @@ -397,10 +399,11 @@ class iterator_buffer final : public buffer { /** * This class fulfills the buffer API by using the container of an contiguous back-insert iterator. * @tparam Container The container type of the back-insert iterator. + * @tparam Capacity The minimum initial requested capacity of the container. */ -template +template requires std::contiguous_iterator -class iterator_buffer> final : public buffer { +class iterator_buffer, Capacity> final : public buffer { public: /** * Constructs and initializes the buffer with the given back-insert iterator. @@ -408,7 +411,7 @@ class iterator_buffer> final : public buffe */ constexpr explicit iterator_buffer(std::back_insert_iterator it) noexcept : container_{detail::get_container(it)} { - static_cast(request_write_area(0, std::min(container_.capacity(), detail::internal_buffer_size))); + static_cast(request_write_area(0, std::min(container_.capacity(), Capacity))); } iterator_buffer(const iterator_buffer&) = delete; @@ -455,8 +458,10 @@ iterator_buffer(Iterator&&) -> iterator_buffer>; /** * This class fulfills the buffer API by using a file stream and an internal cache. + * @tparam CacheSize The size of the internal cache. */ -class file_buffer : public buffer { +template +class file_buffer final : public buffer { public: /** * Constructs and initializes the buffer with the given file stream. @@ -499,14 +504,16 @@ class file_buffer : public buffer { private: std::FILE* file_; - std::array cache_; + std::array cache_; }; /** * This class fulfills the buffer API by using a primary buffer and an internal cache. * Only a limited amount of characters is written to the primary buffer. The remaining characters are truncated. + * @tparam CacheSize The size of the internal cache. */ -class truncating_buffer : public buffer { +template +class truncating_buffer final : public buffer { public: /** * Constructs and initializes the buffer with the given primary buffer and limit. @@ -522,7 +529,7 @@ class truncating_buffer : public buffer { truncating_buffer(truncating_buffer&&) = delete; truncating_buffer& operator=(const truncating_buffer&) = delete; truncating_buffer& operator=(truncating_buffer&&) = delete; - constexpr ~truncating_buffer() noexcept override; + constexpr ~truncating_buffer() noexcept override = default; /** * Returns the count of the total (not truncated) written characters. @@ -564,18 +571,17 @@ class truncating_buffer : public buffer { size_t limit_; size_t written_{}; size_t used_{}; - std::array cache_; + std::array cache_; }; -// Explicit out-of-class definition because of GCC bug: used before its definition. -constexpr truncating_buffer::~truncating_buffer() noexcept = default; - namespace detail { /** * A buffer that counts the number of characters written. Discards the output. + * @tparam CacheSize The size of the internal cache. */ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-member-init): cache_ can be left uninitialized. +template class counting_buffer final : public buffer { public: // NOLINTNEXTLINE(cppcoreguidelines-pro-type-member-init): cache_ can be left uninitialized. @@ -584,7 +590,7 @@ class counting_buffer final : public buffer { constexpr counting_buffer(counting_buffer&&) noexcept = delete; constexpr counting_buffer& operator=(const counting_buffer&) = delete; constexpr counting_buffer& operator=(counting_buffer&&) noexcept = delete; - constexpr ~counting_buffer() override; + constexpr ~counting_buffer() noexcept override = default; /** * Calculates the number of Char's that were written. @@ -607,11 +613,9 @@ class counting_buffer final : public buffer { private: size_t used_{}; - std::array cache_; + std::array cache_; }; -constexpr counting_buffer::~counting_buffer() = default; - } // namespace detail } // namespace emio diff --git a/test/unit_test/test_buffer.cpp b/test/unit_test/test_buffer.cpp index ee0bdf2..3cd5027 100644 --- a/test/unit_test/test_buffer.cpp +++ b/test/unit_test/test_buffer.cpp @@ -21,7 +21,7 @@ TEST_CASE("memory_buffer", "[buffer]") { constexpr size_t first_size{15}; constexpr size_t second_size{55}; - constexpr size_t third_size{emio::detail::internal_buffer_size}; + constexpr size_t third_size{emio::default_cache_size}; const std::string expected_str_part_1(first_size, 'x'); const std::string expected_str_part_2(second_size, 'y'); @@ -144,7 +144,7 @@ TEST_CASE("span_buffer", "[buffer]") { constexpr size_t first_size{15}; constexpr size_t second_size{55}; - constexpr size_t third_size{emio::detail::internal_buffer_size}; + constexpr size_t third_size{emio::default_cache_size}; std::array storage{}; emio::span_buffer buf{storage}; @@ -254,9 +254,9 @@ TEST_CASE("counting_buffer", "[buffer]") { constexpr size_t first_size{15}; constexpr size_t second_size{55}; - constexpr size_t third_size{emio::detail::internal_buffer_size}; + constexpr size_t third_size{emio::default_cache_size}; - using emio::detail::internal_buffer_size; + using emio::default_cache_size; emio::detail::counting_buffer buf; CHECK(buf.count() == 0); @@ -279,13 +279,13 @@ TEST_CASE("counting_buffer", "[buffer]") { CHECK(buf.count() == (first_size + second_size + third_size)); - area = buf.get_write_area_of(internal_buffer_size); + area = buf.get_write_area_of(default_cache_size); REQUIRE(area); - REQUIRE(area->size() == internal_buffer_size); + REQUIRE(area->size() == default_cache_size); - CHECK(buf.count() == (first_size + second_size + third_size + internal_buffer_size)); + CHECK(buf.count() == (first_size + second_size + third_size + default_cache_size)); - area = buf.get_write_area_of(internal_buffer_size + 1); + area = buf.get_write_area_of(default_cache_size + 1); CHECK(!area); CHECK(buf.count() == (first_size + second_size + 2 * third_size)); } @@ -328,11 +328,9 @@ TEST_CASE("iterator_buffer", "[buffer]") { // * Write different data lengths into the buffer to test the internal buffer and flush mechanism. // Expected: At the end (final flush/destruction), everything is written into the string. - using emio::detail::internal_buffer_size; - - const std::string expected_str_part_1(internal_buffer_size, 'x'); + const std::string expected_str_part_1(emio::default_cache_size, 'x'); const std::string expected_str_part_2(2, 'y'); - const std::string expected_str_part_3(internal_buffer_size, 'z'); + const std::string expected_str_part_3(emio::default_cache_size, 'z'); const std::string expected_str_part_4(3, 'x'); const std::string expected_str_part_5(2, 'y'); const std::string expected_str_part_6(42, 'z'); @@ -345,11 +343,11 @@ TEST_CASE("iterator_buffer", "[buffer]") { CHECK(it_buf.out() == s.begin()); CHECK(it_buf.get_write_area_of(std::numeric_limits::max()) == emio::err::eof); - CHECK(it_buf.get_write_area_of(internal_buffer_size + 1) == emio::err::eof); + CHECK(it_buf.get_write_area_of(emio::default_cache_size + 1) == emio::err::eof); - emio::result> area = it_buf.get_write_area_of(internal_buffer_size); + emio::result> area = it_buf.get_write_area_of(emio::default_cache_size); REQUIRE(area); - REQUIRE(area->size() == internal_buffer_size); + REQUIRE(area->size() == emio::default_cache_size); fill(area, 'x'); // No flush yet. @@ -363,9 +361,9 @@ TEST_CASE("iterator_buffer", "[buffer]") { // 1. flush. CHECK(s.starts_with(expected_str_part_1)); - area = it_buf.get_write_area_of(internal_buffer_size); + area = it_buf.get_write_area_of(emio::default_cache_size); REQUIRE(area); - REQUIRE(area->size() == internal_buffer_size); + REQUIRE(area->size() == emio::default_cache_size); fill(area, 'z'); // 2. flush. @@ -465,17 +463,15 @@ TEST_CASE("file_buffer", "[buffer]") { // * Write into the buffer, flush (or not) and read out again. // Expected: Everything is written to the file stream after flush. - using emio::detail::internal_buffer_size; - // Open a temporary file. std::FILE* tmpf = std::tmpfile(); REQUIRE(tmpf); emio::file_buffer file_buf{tmpf}; - std::array read_out_buf{}; + std::array read_out_buf{}; // Write area is limited. - CHECK(file_buf.get_write_area_of(internal_buffer_size + 1) == emio::err::eof); + CHECK(file_buf.get_write_area_of(emio::default_cache_size + 1) == emio::err::eof); // Write into. emio::result> area = file_buf.get_write_area_of(2); @@ -514,11 +510,11 @@ TEST_CASE("file_buffer", "[buffer]") { CHECK(std::fgets(read_out_buf.data(), read_out_buf.size(), tmpf)); CHECK(std::string_view{read_out_buf.data(), 6} == "yyzzzz"); - const std::string expected_long_str_part(internal_buffer_size, 'x'); + const std::string expected_long_str_part(emio::default_cache_size, 'x'); - area = file_buf.get_write_area_of(internal_buffer_size); + area = file_buf.get_write_area_of(emio::default_cache_size); REQUIRE(area); - REQUIRE(area->size() == internal_buffer_size); + REQUIRE(area->size() == emio::default_cache_size); fill(area, 'x'); // Internal flush should have happened. @@ -529,7 +525,7 @@ TEST_CASE("file_buffer", "[buffer]") { std::rewind(tmpf); CHECK(std::fgets(read_out_buf.data(), read_out_buf.size(), tmpf)); - CHECK(std::string_view{read_out_buf.data(), 6 + internal_buffer_size} == "yyzzzz" + expected_long_str_part); + CHECK(std::string_view{read_out_buf.data(), 6 + emio::default_cache_size} == "yyzzzz" + expected_long_str_part); } TEST_CASE("truncating_buffer", "[buffer]") { @@ -570,10 +566,10 @@ TEST_CASE("truncating_buffer", "[buffer]") { } SECTION("request more than limit (3)") { - area = buf.get_write_area_of(emio::detail::internal_buffer_size); + area = buf.get_write_area_of(emio::default_cache_size); REQUIRE(area); fill(area, 'c'); - CHECK(buf.count() == 48 + emio::detail::internal_buffer_size); + CHECK(buf.count() == 48 + emio::default_cache_size); // not flushed CHECK(primary_buf.view().size() == 48); @@ -585,10 +581,10 @@ TEST_CASE("truncating_buffer", "[buffer]") { } } SECTION("request more than limit (2)") { - area = buf.get_write_area_of(emio::detail::internal_buffer_size); + area = buf.get_write_area_of(emio::default_cache_size); REQUIRE(area); fill(area, 'b'); - CHECK(buf.count() == 40 + emio::detail::internal_buffer_size); + CHECK(buf.count() == 40 + emio::default_cache_size); // not flushed CHECK(primary_buf.view().size() == 40); @@ -603,10 +599,10 @@ TEST_CASE("truncating_buffer", "[buffer]") { const std::string expected_string = std::string(48, 'a'); emio::truncating_buffer buf{primary_buf, 48}; - emio::result> area = buf.get_write_area_of(emio::detail::internal_buffer_size); + emio::result> area = buf.get_write_area_of(emio::default_cache_size); REQUIRE(area); fill(area, 'a'); - CHECK(buf.count() == emio::detail::internal_buffer_size); + CHECK(buf.count() == emio::default_cache_size); // not flushed CHECK(primary_buf.view().size() == 0); @@ -618,8 +614,8 @@ TEST_CASE("truncating_buffer", "[buffer]") { } SECTION("request more than limit (2)") { - CHECK(buf.get_write_area_of(emio::detail::internal_buffer_size)); - CHECK(buf.count() == 2 * emio::detail::internal_buffer_size); + CHECK(buf.get_write_area_of(emio::default_cache_size)); + CHECK(buf.count() == 2 * emio::default_cache_size); // not flushed CHECK(primary_buf.view().size() == 48); @@ -657,7 +653,7 @@ TEST_CASE("truncating_buffer", "[buffer]") { CHECK(buf.count() == 68); // Requesting more will fail because primary buffer is too small. - CHECK_FALSE(buf.get_write_area_of(emio::detail::internal_buffer_size)); + CHECK_FALSE(buf.get_write_area_of(emio::default_cache_size)); CHECK(primary_buf.view().size() == 64); CHECK(primary_buf.view() == expected_string); @@ -684,9 +680,9 @@ TEST_CASE("truncating_buffer", "[buffer]") { const std::string expected_string = std::string(64, 'a'); emio::truncating_buffer buf{primary_buf, 64}; - emio::result> area = buf.get_write_area_of_max(emio::detail::internal_buffer_size + 10); + emio::result> area = buf.get_write_area_of_max(emio::default_cache_size + 10); REQUIRE(area); - CHECK(area->size() == emio::detail::internal_buffer_size); + CHECK(area->size() == emio::default_cache_size); fill(area, 'a'); // not flushed diff --git a/test/unit_test/test_writer.cpp b/test/unit_test/test_writer.cpp index 8e81912..b110dc2 100644 --- a/test/unit_test/test_writer.cpp +++ b/test/unit_test/test_writer.cpp @@ -119,17 +119,15 @@ TEST_CASE("writer", "[writer]") { TEST_CASE("writer with cached buffer", "[writer]") { // Test strategy: // * Construct a writer from a cached buffer (output iterator). - // * Write long strings (> internal_buffer_size) with the writer into the buffer. + // * Write long strings (> emio::default_cache_size) with the writer into the buffer. // Expected: The string methods work as expected because the string is written in chunks. - using emio::detail::internal_buffer_size; - - const std::string expected_str_part_1(internal_buffer_size + 1, 'x'); - const std::string expected_str_part_2(internal_buffer_size + 2, 'y'); - const std::string expected_str_part_3(internal_buffer_size + 3, 'z'); + const std::string expected_str_part_1(emio::default_cache_size + 1, 'x'); + const std::string expected_str_part_2(emio::default_cache_size + 2, 'y'); + const std::string expected_str_part_3(emio::default_cache_size + 3, 'z'); std::string storage; - storage.resize(5 * internal_buffer_size); + storage.resize(5 * emio::default_cache_size); emio::iterator_buffer buf{storage.begin()}; emio::writer writer{buf};