diff --git a/CHANGELOG.md b/CHANGELOG.md index 6b63170b..11c020d7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +Version 198: + +* flat_buffer improvements + +-------------------------------------------------------------------------------- + Version 197: * Improvements to echo-op example diff --git a/include/boost/beast/core/flat_buffer.hpp b/include/boost/beast/core/flat_buffer.hpp index 17c4111a..76438d5e 100644 --- a/include/boost/beast/core/flat_buffer.hpp +++ b/include/boost/beast/core/flat_buffer.hpp @@ -16,6 +16,7 @@ #include #include #include +#include namespace boost { namespace beast { @@ -61,11 +62,6 @@ class basic_flat_buffer template rebind_alloc> #endif { - enum - { - min_size = 512 - }; - template friend class basic_flat_buffer; @@ -73,9 +69,18 @@ class basic_flat_buffer detail::allocator_traits:: template rebind_alloc; + static bool constexpr default_nothrow = + std::is_nothrow_default_constructible::value; + using alloc_traits = detail::allocator_traits; + using pocma = typename + alloc_traits::propagate_on_container_move_assignment; + + using pocca = typename + alloc_traits::propagate_on_container_copy_assignment; + static std::size_t dist(char const* first, char const* last) @@ -99,157 +104,209 @@ public: /** Constructor - Upon construction, @ref capacity will return zero. + After construction, @ref capacity will return zero, and + @ref max_size will return the largest value which may + be passed to the allocator's `allocate` function. */ - basic_flat_buffer(); + basic_flat_buffer() noexcept(default_nothrow); /** Constructor - Upon construction, @ref capacity will return zero. + After construction, @ref capacity will return zero, and + @ref max_size will return the specified value of `limit`. - @param limit The setting for @ref max_size. + @param limit The desired maximum size. */ explicit - basic_flat_buffer(std::size_t limit); + basic_flat_buffer( + std::size_t limit) noexcept(default_nothrow); /** Constructor - Upon construction, @ref capacity will return zero. + After construction, @ref capacity will return zero, and + @ref max_size will return the largest value which may + be passed to the allocator's `allocate` function. - @param alloc The allocator to construct with. + @param alloc The allocator to use for the object. + + @par Exception Safety + + No-throw guarantee. */ explicit - basic_flat_buffer(Allocator const& alloc); + basic_flat_buffer(Allocator const& alloc) noexcept; /** Constructor - Upon construction, @ref capacity will return zero. + After construction, @ref capacity will return zero, and + @ref max_size will return the specified value of `limit`. - @param limit The setting for @ref max_size. + @param limit The desired maximum size. - @param alloc The allocator to use. + @param alloc The allocator to use for the object. + + @par Exception Safety + + No-throw guarantee. */ basic_flat_buffer( - std::size_t limit, Allocator const& alloc); + std::size_t limit, + Allocator const& alloc) noexcept; /** Move Constructor - Constructs the container with the contents of other - using move semantics. After the move, other is - guaranteed to be empty. + The container is constructed with the contents of `other` + using move semantics. The maximum size will be the same + as the moved-from object. - Buffer sequences previously obtained using @ref data - or @ref prepare are not invalidated after the move. + Buffer sequences previously obtained from `other` using + @ref data or @ref prepare remain valid after the move. - @param other The object to move from. After the move, - the moved-from object's state will be as if default - constructed using its current allocator and limit. + @param other The object to move from. After the move, the + moved-from object will have zero capacity, zero readable + bytes, and zero writable bytes. + + @par Exception Safety + + No-throw guarantee. */ - basic_flat_buffer(basic_flat_buffer&& other); + basic_flat_buffer(basic_flat_buffer&& other) noexcept; /** Move Constructor - Using alloc as the allocator for the new container, the - contents of other are moved. If `alloc != other.get_allocator()`, - this results in a copy. After the move, other is - guaranteed to be empty. + Using `alloc` as the allocator for the new container, the + contents of `other` are moved. If `alloc != other.get_allocator()`, + this results in a copy. The maximum size will be the same + as the moved-from object. - All buffers sequences previously obtained using - @ref data or @ref prepare are invalidated. + Buffer sequences previously obtained from `other` using + @ref data or @ref prepare become invalid after the move. @param other The object to move from. After the move, - the moved-from object's state will be as if default - constructed using its current allocator and limit. + the moved-from object will have zero capacity, zero readable + bytes, and zero writable bytes. - @param alloc The allocator to use for the newly - constructed object. + @param alloc The allocator to use for the object. + + @throws std::length_error if `other.size()` exceeds the + maximum allocation size of `alloc`. */ basic_flat_buffer( - basic_flat_buffer&& other, Allocator const& alloc); + basic_flat_buffer&& other, + Allocator const& alloc); /** Copy Constructor - The newly constructed object will have a copy of the - allocator and contents of other, and zero writable bytes. + This container is constructed with the contents of `other` + using copy semantics. The maximum size will be the same + as the copied object. @param other The object to copy from. + + @throws std::length_error if `other.size()` exceeds the + maximum allocation size of the allocator. */ basic_flat_buffer(basic_flat_buffer const& other); /** Copy Constructor - The newly constructed object will have a copy of the - specified allocator, a copy of the contents of other, - and zero writable bytes. + This container is constructed with the contents of `other` + using copy semantics and the specified allocator. The maximum + size will be the same as the copied object. @param other The object to copy from. - @param alloc The allocator to use. + @param alloc The allocator to use for the object. + + @throws std::length_error if `other.size()` exceeds the + maximum allocation size of `alloc`. */ - basic_flat_buffer(basic_flat_buffer const& other, + basic_flat_buffer( + basic_flat_buffer const& other, Allocator const& alloc); /** Copy Constructor - The newly constructed object will have a copy of the - contents of other, and zero writable bytes. + This container is constructed with the contents of `other` + using copy semantics. The maximum size will be the same + as the copied object. @param other The object to copy from. + + @throws std::length_error if `other.size()` exceeds the + maximum allocation size of the allocator. */ template basic_flat_buffer( - basic_flat_buffer const& other); + basic_flat_buffer const& other) + noexcept(default_nothrow);; /** Copy Constructor - The newly constructed object will have a copy of the - specified allocator, a copy of the contents of other, - and zero writable bytes. + This container is constructed with the contents of `other` + using copy semantics. The maximum size will be the same + as the copied object. @param other The object to copy from. - @param alloc The allocator to use. + @param alloc The allocator to use for the object. + + @throws std::length_error if `other.size()` exceeds the + maximum allocation size of `alloc`. */ template basic_flat_buffer( basic_flat_buffer const& other, - Allocator const& alloc); + Allocator const& alloc); /** Move Assignment - Assigns the container with the contents of other - using move semantics. After the move, other is - guaranteed to be empty. The previous contents of - this container are deleted. + The container is assigned with the contents of `other` + using move semantics. The maximum size will be the same + as the moved-from object. - Buffer sequences previously obtained using @ref data - or @ref prepare are not invalidated after the move. + Buffer sequences previously obtained from `other` using + @ref data or @ref prepare remain valid after the move. @param other The object to move from. After the move, - the moved-from object's state will be as if default - constructed using its current allocator and limit. + the moved-from object will have zero capacity, zero readable + bytes, and zero writable bytes. + + @par Exception Safety + + No-throw guarantee. */ basic_flat_buffer& - operator=(basic_flat_buffer&& other); + operator=(basic_flat_buffer&& other) noexcept; /** Copy Assignment - The assigned object will have a copy of the allocator - and contents of other, and zero writable bytes. The - previous contents of this container are deleted. + The container is assigned with the contents of `other` + using copy semantics. The maximum size will be the same + as the copied object. + + After the copy, `this` will have zero writable bytes. @param other The object to copy from. + + @throws std::length_error if `other.size()` exceeds the + maximum allocation size of the allocator. */ basic_flat_buffer& operator=(basic_flat_buffer const& other); /** Copy assignment - The assigned object will have a copy of the contents - of other, and zero writable bytes. The previous contents - of this container are deleted. + The container is assigned with the contents of `other` + using copy semantics. The maximum size will be the same + as the copied object. + + After the copy, `this` will have zero writable bytes. @param other The object to copy from. + + @throws std::length_error if `other.size()` exceeds the + maximum allocation size of the allocator. */ template basic_flat_buffer& @@ -262,10 +319,53 @@ public: return this->get(); } + /** Set the maximum allowed capacity + + This function changes the currently configured upper limit + on capacity to the specified value. + + @param n The maximum number of bytes ever allowed for capacity. + + @par Exception Safety + + No-throw guarantee. + */ + void + max_size(std::size_t n) noexcept + { + max_ = n; + } + + /** Guarantee a minimum capacity + + This function adjusts the internal storage (if necessary) + to guarantee space for at least `n` bytes. + + Buffer sequences previously obtained using @ref data or + @ref prepare become invalid. + + @param n The minimum number of byte for the new capacity. + If this value is greater than the maximum size, then the + maximum size will be adjusted upwards to this value. + + @par Exception Safety + + Basic guarantee. + + @throws std::length_error if n is larger than the maximum + allocation size of the allocator. + */ + void + reserve(std::size_t n); + /** Reallocate the buffer to fit the readable bytes exactly. - All buffers sequences previously obtained using - @ref data or @ref prepare are invalidated. + Buffer sequences previously obtained using @ref data or + @ref prepare become invalid. + + @par Exception Safety + + Strong guarantee. */ void shrink_to_fit(); @@ -338,12 +438,17 @@ public: reallocated as needed. All buffers sequences previously obtained using - @ref data or @ref prepare are invalidated. + @ref data or @ref prepare become invalid. @param n The desired number of bytes in the returned buffer sequence. - @throws std::length_error if `size() + n` exceeds `max_size()`. + @throws std::length_error if `size() + n` exceeds either + `max_size()` or the allocator's maximum allocation size. + + @par Exception Safety + + Strong guarantee. */ mutable_buffers_type prepare(std::size_t n); @@ -356,11 +461,15 @@ public: bytes, all writable bytes are appended to the readable bytes. All buffers sequences previously obtained using - @ref data or @ref prepare are invalidated. + @ref data or @ref prepare become invalid. @param n The number of bytes to append. If this number is greater than the number of writable bytes, all writable bytes are appended. + + @par Exception Safety + + No-throw guarantee. */ void commit(std::size_t n) noexcept @@ -373,21 +482,22 @@ public: Removes n bytes from the beginning of the readable bytes. All buffers sequences previously obtained using - @ref data or @ref prepare are invalidated. + @ref data or @ref prepare become invalid. @param n The number of bytes to remove. If this number is greater than the number of readable bytes, all readable bytes are removed. + + @par Exception Safety + + No-throw guarantee. */ void consume(std::size_t n) noexcept; private: - void - reset(); - - template - void copy_from(DynamicBuffer const& other); + template + void copy_from(basic_flat_buffer const& other); void move_assign(basic_flat_buffer&, std::true_type); void move_assign(basic_flat_buffer&, std::false_type); void copy_assign(basic_flat_buffer const&, std::true_type); @@ -395,6 +505,8 @@ private: void swap(basic_flat_buffer&); void swap(basic_flat_buffer&, std::true_type); void swap(basic_flat_buffer&, std::false_type); + char* alloc(std::size_t n); + void clear(); }; /// A flat buffer which uses the default allocator. diff --git a/include/boost/beast/core/impl/flat_buffer.ipp b/include/boost/beast/core/impl/flat_buffer.ipp index e2672871..49c37fe4 100644 --- a/include/boost/beast/core/impl/flat_buffer.ipp +++ b/include/boost/beast/core/impl/flat_buffer.ipp @@ -13,6 +13,7 @@ #include #include #include +#include #include namespace boost { @@ -31,53 +32,55 @@ basic_flat_buffer:: { if(begin_) alloc_traits::deallocate( - this->get(), begin_, dist(begin_, end_)); + this->get(), begin_, capacity()); } template basic_flat_buffer:: -basic_flat_buffer() +basic_flat_buffer() noexcept(default_nothrow) : begin_(nullptr) , in_(nullptr) , out_(nullptr) , last_(nullptr) , end_(nullptr) - , max_((std::numeric_limits< - std::size_t>::max)()) -{ -} - -template -basic_flat_buffer:: -basic_flat_buffer(std::size_t limit) - : begin_(nullptr) - , in_(nullptr) - , out_(nullptr) - , last_(nullptr) - , end_(nullptr) - , max_(limit) -{ -} - -template -basic_flat_buffer:: -basic_flat_buffer(Allocator const& alloc) - : boost::empty_value( - boost::empty_init_t(), alloc) - , begin_(nullptr) - , in_(nullptr) - , out_(nullptr) - , last_(nullptr) - , end_(nullptr) - , max_((std::numeric_limits< - std::size_t>::max)()) + , max_(alloc_traits::max_size( + this->get())) { } template basic_flat_buffer:: basic_flat_buffer( - std::size_t limit, Allocator const& alloc) + std::size_t limit) noexcept(default_nothrow) + : begin_(nullptr) + , in_(nullptr) + , out_(nullptr) + , last_(nullptr) + , end_(nullptr) + , max_(limit) +{ +} + +template +basic_flat_buffer:: +basic_flat_buffer(Allocator const& alloc) noexcept + : boost::empty_value( + boost::empty_init_t(), alloc) + , begin_(nullptr) + , in_(nullptr) + , out_(nullptr) + , last_(nullptr) + , end_(nullptr) + , max_(alloc_traits::max_size( + this->get())) +{ +} + +template +basic_flat_buffer:: +basic_flat_buffer( + std::size_t limit, + Allocator const& alloc) noexcept : boost::empty_value( boost::empty_init_t(), alloc) , begin_(nullptr) @@ -91,7 +94,7 @@ basic_flat_buffer( template basic_flat_buffer:: -basic_flat_buffer(basic_flat_buffer&& other) +basic_flat_buffer(basic_flat_buffer&& other) noexcept : boost::empty_value( boost::empty_init_t(), std::move(other.get())) , begin_(boost::exchange(other.begin_, nullptr)) @@ -106,7 +109,8 @@ basic_flat_buffer(basic_flat_buffer&& other) template basic_flat_buffer:: basic_flat_buffer( - basic_flat_buffer&& other, Allocator const& alloc) + basic_flat_buffer&& other, + Allocator const& alloc) : boost::empty_value( boost::empty_init_t(), alloc) { @@ -119,7 +123,7 @@ basic_flat_buffer( end_ = nullptr; max_ = other.max_; copy_from(other); - other.reset(); + other.clear(); } else { @@ -129,6 +133,9 @@ basic_flat_buffer( last_ = other.out_; // invalidate end_ = other.end_; max_ = other.max_; + BOOST_ASSERT( + alloc_traits::max_size(this->get()) == + alloc_traits::max_size(other.get())); other.begin_ = nullptr; other.in_ = nullptr; other.out_ = nullptr; @@ -155,9 +162,11 @@ basic_flat_buffer(basic_flat_buffer const& other) template basic_flat_buffer:: -basic_flat_buffer(basic_flat_buffer const& other, - Allocator const& alloc) - : boost::empty_value(boost::empty_init_t(), alloc) +basic_flat_buffer( + basic_flat_buffer const& other, + Allocator const& alloc) + : boost::empty_value( + boost::empty_init_t(), alloc) , begin_(nullptr) , in_(nullptr) , out_(nullptr) @@ -172,7 +181,8 @@ template template basic_flat_buffer:: basic_flat_buffer( - basic_flat_buffer const& other) + basic_flat_buffer const& other) + noexcept(default_nothrow) : begin_(nullptr) , in_(nullptr) , out_(nullptr) @@ -186,9 +196,11 @@ basic_flat_buffer( template template basic_flat_buffer:: -basic_flat_buffer(basic_flat_buffer const& other, - Allocator const& alloc) - : boost::empty_value(boost::empty_init_t(), alloc) +basic_flat_buffer( + basic_flat_buffer const& other, + Allocator const& alloc) + : boost::empty_value( + boost::empty_init_t(), alloc) , begin_(nullptr) , in_(nullptr) , out_(nullptr) @@ -202,12 +214,11 @@ basic_flat_buffer(basic_flat_buffer const& other, template auto basic_flat_buffer:: -operator=(basic_flat_buffer&& other) -> +operator=(basic_flat_buffer&& other) noexcept -> basic_flat_buffer& { if(this != &other) - move_assign(other, std::integral_constant{}); + move_assign(other, pocma{}); return *this; } @@ -218,8 +229,7 @@ operator=(basic_flat_buffer const& other) -> basic_flat_buffer& { if(this != &other) - copy_assign(other, std::integral_constant{}); + copy_assign(other, pocca{}); return *this; } @@ -227,15 +237,25 @@ template template auto basic_flat_buffer:: -operator=(basic_flat_buffer const& other) -> +operator=( + basic_flat_buffer const& other) -> basic_flat_buffer& { - reset(); - max_ = other.max_; copy_from(other); return *this; } +template +void +basic_flat_buffer:: +reserve(std::size_t n) +{ + if(max_ < n) + max_ = n; + if(capacity() < n) + prepare(n - capacity()); +} + template void basic_flat_buffer:: @@ -249,8 +269,7 @@ shrink_to_fit() { BOOST_ASSERT(begin_); BOOST_ASSERT(in_); - p = alloc_traits::allocate( - this->get(), len); + p = alloc(len); std::memcpy(p, in_, len); } else @@ -258,7 +277,7 @@ shrink_to_fit() p = nullptr; } alloc_traits::deallocate( - this->get(), begin_, this->dist(begin_, end_)); + this->get(), begin_, this->capacity()); begin_ = p; in_ = begin_; out_ = begin_ + len; @@ -300,8 +319,7 @@ prepare(std::size_t n) -> auto const new_size = (std::min)( max_, (std::max)(2 * len, len + n)); - auto const p = alloc_traits::allocate( - this->get(), new_size); + auto const p = alloc(new_size); if(begin_) { BOOST_ASSERT(p); @@ -335,24 +353,28 @@ consume(std::size_t n) noexcept //------------------------------------------------------------------------------ template +template void basic_flat_buffer:: -reset() +copy_from( + basic_flat_buffer const& other) { - consume(size()); - shrink_to_fit(); -} - -template -template -void -basic_flat_buffer:: -copy_from(DynamicBuffer const& buffer) -{ - if(buffer.size() == 0) - return; - commit(net::buffer_copy( - prepare(buffer.size()), buffer.data())); + std::size_t const n = other.size(); + if(begin_ && (n == 0 || n > capacity())) + clear(); + if(n > capacity()) + { + BOOST_ASSERT(! begin_); + begin_ = alloc(n); + in_ = begin_; + out_ = begin_ + n; + last_ = begin_ + n; + end_ = begin_ + n; + } + in_ = begin_; + out_ = begin_ + n; + last_ = begin_ + n; + std::memcpy(begin_, other.begin_, n); } template @@ -360,7 +382,7 @@ void basic_flat_buffer:: move_assign(basic_flat_buffer& other, std::true_type) { - reset(); + clear(); this->get() = std::move(other.get()); begin_ = other.begin_; in_ = other.in_; @@ -380,11 +402,10 @@ void basic_flat_buffer:: move_assign(basic_flat_buffer& other, std::false_type) { - reset(); if(this->get() != other.get()) { copy_from(other); - other.reset(); + other.clear(); } else { @@ -397,7 +418,6 @@ void basic_flat_buffer:: copy_assign(basic_flat_buffer const& other, std::true_type) { - reset(); max_ = other.max_; this->get() = other.get(); copy_from(other); @@ -408,7 +428,7 @@ void basic_flat_buffer:: copy_assign(basic_flat_buffer const& other, std::false_type) { - reset(); + clear(); max_ = other.max_; copy_from(other); } @@ -463,6 +483,31 @@ swap( lhs.swap(rhs); } +template +char* +basic_flat_buffer:: +alloc(std::size_t n) +{ + if(n > alloc_traits::max_size(this->get())) + BOOST_THROW_EXCEPTION(std::length_error( + "A basic_flat_buffer exceeded the allocator's maximum size")); + return alloc_traits::allocate(this->get(), n); +} + +template +void +basic_flat_buffer:: +clear() +{ + alloc_traits::deallocate( + this->get(), begin_, size()); + begin_ = nullptr; + in_ = nullptr; + out_ = nullptr; + last_ = nullptr; + end_ = nullptr; +} + } // beast } // boost diff --git a/test/beast/core/flat_buffer.cpp b/test/beast/core/flat_buffer.cpp index b400b20d..f4c58d18 100644 --- a/test/beast/core/flat_buffer.cpp +++ b/test/beast/core/flat_buffer.cpp @@ -292,8 +292,10 @@ public: BEAST_EXPECT(buffers_to_string(b2.data()) == s.substr(7)); } { - flat_buffer b2{64}; + flat_buffer b2{32}; + BEAST_EXPECT(b2.max_size() == 32); b2 = b1; + BEAST_EXPECT(b2.max_size() == b1.max_size()); BEAST_EXPECT(buffers_to_string(b2.data()) == s); b2.consume(7); BEAST_EXPECT(buffers_to_string(b2.data()) == s.substr(7)); @@ -309,6 +311,14 @@ public: BEAST_EXPECT(buffers_to_string(b.data()) == "4567890123"); } + // max_size + { + flat_buffer b{10}; + BEAST_EXPECT(b.max_size() == 10); + b.max_size(32); + BEAST_EXPECT(b.max_size() == 32); + } + // read_size { flat_buffer b{10}; @@ -372,6 +382,18 @@ public: } } + // reserve + { + flat_buffer b; + BEAST_EXPECT(b.capacity() == 0); + b.reserve(50); + BEAST_EXPECT(b.capacity() == 50); + b.prepare(20); + b.commit(20); + b.reserve(50); + BEAST_EXPECT(b.capacity() == 50); + } + // shrink to fit { flat_buffer b;