diff --git a/CHANGELOG.md b/CHANGELOG.md index 29f77270..dfdb3f1d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ Version 194: * Add net namespace alias * Don't use-after-free in test * Tidy up ssl_stream (experimental) +* Dynamic buffer improvements -------------------------------------------------------------------------------- diff --git a/include/boost/beast/core/flat_buffer.hpp b/include/boost/beast/core/flat_buffer.hpp index 1e0f0ef5..14cc046e 100644 --- a/include/boost/beast/core/flat_buffer.hpp +++ b/include/boost/beast/core/flat_buffer.hpp @@ -20,18 +20,30 @@ namespace boost { namespace beast { -/** A linear dynamic buffer. +/** A dynamic buffer providing buffer sequences of length one. + + A dynamic buffer encapsulates memory storage that may be + automatically resized as required, where the memory is + divided into two regions: readable bytes followed by + writable bytes. These memory regions are internal to + the dynamic buffer, but direct access to the elements + is provided to permit them to be efficiently used with + I/O operations. Objects of this type meet the requirements of @b DynamicBuffer - and offer additional invariants: - - @li Buffer sequences returned by @ref data and @ref prepare - will always be of length one. + and have the following additional properties: + + @li A mutable buffer sequence representing the readable + bytes is returned by @ref data when `this` is non-const. @li A configurable maximum buffer size may be set upon construction. Attempts to exceed the buffer size will throw `std::length_error`. + @li Buffer sequences representing the readable and writable + bytes, returned by @ref data and @ref prepare, will have + length one. + Upon construction, a maximum size for the buffer may be specified. If this limit is exceeded, the `std::length_error` exception will be thrown. @@ -65,7 +77,6 @@ class basic_flat_buffer detail::allocator_traits; static - inline std::size_t dist(char const* first, char const* last) { @@ -83,24 +94,18 @@ public: /// The type of allocator used. using allocator_type = Allocator; - /// The type used to represent the input sequence as a list of buffers. - using const_buffers_type = boost::asio::const_buffer; - - /// The type used to represent the output sequence as a list of buffers. - using mutable_buffers_type = boost::asio::mutable_buffer; - /// Destructor ~basic_flat_buffer(); /** Constructor - Upon construction, capacity will be zero. + Upon construction, @ref capacity will return zero. */ basic_flat_buffer(); /** Constructor - Upon construction, capacity will be zero. + Upon construction, @ref capacity will return zero. @param limit The setting for @ref max_size. */ @@ -109,7 +114,7 @@ public: /** Constructor - Upon construction, capacity will be zero. + Upon construction, @ref capacity will return zero. @param alloc The allocator to construct with. */ @@ -118,7 +123,7 @@ public: /** Constructor - Upon construction, capacity will be zero. + Upon construction, @ref capacity will return zero. @param limit The setting for @ref max_size. @@ -127,36 +132,55 @@ public: basic_flat_buffer( std::size_t limit, Allocator const& alloc); - /** Constructor + /** Move Constructor - After the move, `*this` will have an empty output sequence. + Constructs the container with the contents of other + using move semantics. After the move, other is + guaranteed to be empty. + + Buffer sequences previously obtained using @ref data + or @ref prepare are not invalidated after the move. @param other The object to move from. After the move, - The object's state will be as if constructed using - its current allocator and limit. + the moved-from object's state will be as if default + constructed using its current allocator and limit. */ basic_flat_buffer(basic_flat_buffer&& other); - /** Constructor + /** Move Constructor - After the move, `*this` will have an empty output sequence. + 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. + + All buffers sequences previously obtained using + @ref data or @ref prepare are invalidated. @param other The object to move from. After the move, - The object's state will be as if constructed using - its current allocator and limit. + the moved-from object's state will be as if default + constructed using its current allocator and limit. - @param alloc The allocator to use. + @param alloc The allocator to use for the newly + constructed object. */ basic_flat_buffer( basic_flat_buffer&& other, Allocator const& alloc); - /** Constructor + /** Copy Constructor + + The newly constructed object will have a copy of the + allocator and contents of other, and zero writable bytes. @param other The object to copy from. */ basic_flat_buffer(basic_flat_buffer const& other); - /** Constructor + /** 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. @param other The object to copy from. @@ -165,7 +189,10 @@ public: basic_flat_buffer(basic_flat_buffer const& other, Allocator const& alloc); - /** Constructor + /** Copy Constructor + + The newly constructed object will have a copy of the + contents of other, and zero writable bytes. @param other The object to copy from. */ @@ -173,7 +200,11 @@ public: basic_flat_buffer( basic_flat_buffer const& other); - /** Constructor + /** 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. @param other The object to copy from. @@ -184,20 +215,28 @@ public: basic_flat_buffer const& other, Allocator const& alloc); - /** Assignment + /** Move Assignment - After the move, `*this` will have an empty output sequence. + 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. + + Buffer sequences previously obtained using @ref data + or @ref prepare are not invalidated after the move. @param other The object to move from. After the move, - the object's state will be as if constructed using - its current allocator and limit. + the moved-from object's state will be as if default + constructed using its current allocator and limit. */ basic_flat_buffer& operator=(basic_flat_buffer&& other); - /** Assignment + /** Copy Assignment - After the copy, `*this` will have an empty output sequence. + 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. @param other The object to copy from. */ @@ -206,7 +245,9 @@ public: /** Copy assignment - After the copy, `*this` will have an empty output sequence. + The assigned object will have a copy of the contents + of other, and zero writable bytes. The previous contents + of this container are deleted. @param other The object to copy from. */ @@ -214,123 +255,149 @@ public: basic_flat_buffer& operator=(basic_flat_buffer const& other); - /// Returns a copy of the associated allocator. + /// Returns a copy of the allocator used. allocator_type get_allocator() const { return this->get(); } - /// Returns the size of the input sequence. - std::size_t - size() const - { - return dist(in_, out_); - } + /** Reallocate the buffer to fit the readable bytes exactly. - /// Return the maximum sum of the input and output sequence sizes. - std::size_t - max_size() const - { - return max_; - } - - /// Return the maximum sum of input and output sizes that can be held without an allocation. - std::size_t - capacity() const - { - return dist(begin_, end_); - } - - /// Get a list of buffers that represent the input sequence. - const_buffers_type - data() const - { - return {in_, dist(in_, out_)}; - } - - /** Get a list of buffers that represent the output sequence, with the given size. - - @throws std::length_error if `size() + n` exceeds `max_size()`. - - @note All previous buffers sequences obtained from - calls to @ref data or @ref prepare are invalidated. - */ - mutable_buffers_type - prepare(std::size_t n); - - /** Move bytes from the output sequence to the input sequence. - - @param n The number of bytes to move. If this is larger than - the number of bytes in the output sequences, then the entire - output sequences is moved. - - @note All previous buffers sequences obtained from - calls to @ref data or @ref prepare are invalidated. - */ - void - commit(std::size_t n) - { - out_ += (std::min)(n, dist(out_, last_)); - } - - /** Remove bytes from the input sequence. - - If `n` is greater than the number of bytes in the input - sequence, all bytes in the input sequence are removed. - - @note All previous buffers sequences obtained from - calls to @ref data or @ref prepare are invalidated. - */ - void - consume(std::size_t n); - - /** Reallocate the buffer to fit the input sequence. - - @note All previous buffers sequences obtained from - calls to @ref data or @ref prepare are invalidated. + All buffers sequences previously obtained using + @ref data or @ref prepare are invalidated. */ void shrink_to_fit(); - /// Exchange two flat buffers + /// Exchange two dynamic buffers template friend void swap( - basic_flat_buffer& lhs, - basic_flat_buffer& rhs); + basic_flat_buffer&, + basic_flat_buffer&); + + //-------------------------------------------------------------------------- + + /// The ConstBufferSequence used to represent the readable bytes. + using const_buffers_type = boost::asio::const_buffer; + + /// The MutableBufferSequence used to represent the readable bytes. + using mutable_data_type = boost::asio::mutable_buffer; + + /// The MutableBufferSequence used to represent the writable bytes. + using mutable_buffers_type = boost::asio::mutable_buffer; + + /// Returns the number of readable bytes. + std::size_t + size() const noexcept + { + return dist(in_, out_); + } + + /// Return the maximum number of bytes, both readable and writable, that can ever be held. + std::size_t + max_size() const noexcept + { + return max_; + } + + /// Return the maximum number of bytes, both readable and writable, that can be held without requiring an allocation. + std::size_t + capacity() const noexcept + { + return dist(begin_, end_); + } + + /// Returns a constant buffer sequence representing the readable bytes + const_buffers_type + data() const noexcept + { + return {in_, dist(in_, out_)}; + } + + /// Returns a constant buffer sequence representing the readable bytes + const_buffers_type + cdata() const noexcept + { + return data(); + } + + /// Returns a mutable buffer sequence representing the readable bytes + mutable_data_type + data() noexcept + { + return {in_, dist(in_, out_)}; + } + + /** Returns a mutable buffer sequence representing writable bytes. + + Returns a mutable buffer sequence representing the writable + bytes containing exactly `n` bytes of storage. Memory may be + reallocated as needed. + + All buffers sequences previously obtained using + @ref data or @ref prepare are invalidated. + + @param n The desired number of bytes in the returned buffer + sequence. + + @throws std::length_error if `size() + n` exceeds `max_size()`. + */ + mutable_buffers_type + prepare(std::size_t n); + + /** Append writable bytes to the readable bytes. + + Appends n bytes from the start of the writable bytes to the + end of the readable bytes. The remainder of the writable bytes + are discarded. If n is greater than the number of writable + bytes, all writable bytes are appended to the readable bytes. + + All buffers sequences previously obtained using + @ref data or @ref prepare are invalidated. + + @param n The number of bytes to append. If this number + is greater than the number of writable bytes, all + writable bytes are appended. + */ + void + commit(std::size_t n) noexcept + { + out_ += (std::min)(n, dist(out_, last_)); + } + + /** Remove bytes from beginning of the readable bytes. + + Removes n bytes from the beginning of the readable bytes. + + All buffers sequences previously obtained using + @ref data or @ref prepare are invalidated. + + @param n The number of bytes to remove. If this number + is greater than the number of readable bytes, all + readable bytes are removed. + */ + void + consume(std::size_t n) noexcept; private: void reset(); template - void - copy_from(DynamicBuffer 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); - - void - copy_assign(basic_flat_buffer const&, std::false_type); - - void - swap(basic_flat_buffer&); - - void - swap(basic_flat_buffer&, std::true_type); - - void - swap(basic_flat_buffer&, std::false_type); + void copy_from(DynamicBuffer 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); + void copy_assign(basic_flat_buffer const&, std::false_type); + void swap(basic_flat_buffer&); + void swap(basic_flat_buffer&, std::true_type); + void swap(basic_flat_buffer&, std::false_type); }; +/// A flat buffer which uses the default allocator. using flat_buffer = basic_flat_buffer>; diff --git a/include/boost/beast/core/flat_static_buffer.hpp b/include/boost/beast/core/flat_static_buffer.hpp index c8eae909..a4ae62f7 100644 --- a/include/boost/beast/core/flat_static_buffer.hpp +++ b/include/boost/beast/core/flat_static_buffer.hpp @@ -19,20 +19,32 @@ namespace boost { namespace beast { -/** A flat @b DynamicBuffer with a fixed size internal buffer. +/** A dynamic buffer using a fixed size internal buffer. - Buffer sequences returned by @ref data and @ref prepare - will always be of length one. - Ownership of the underlying storage belongs to the derived class. + A dynamic buffer encapsulates memory storage that may be + automatically resized as required, where the memory is + divided into two regions: readable bytes followed by + writable bytes. These memory regions are internal to + the dynamic buffer, but direct access to the elements + is provided to permit them to be efficiently used with + I/O operations. + + Objects of this type meet the requirements of @b DynamicBuffer + and have the following additional properties: + + @li A mutable buffer sequence representing the readable + bytes is returned by @ref data when `this` is non-const. + + @li Buffer sequences representing the readable and writable + bytes, returned by @ref data and @ref prepare, will have + length one. + + @li Ownership of the underlying storage belongs to the + derived class. @note Variables are usually declared using the template class - @ref flat_static_buffer; however, to reduce the number of instantiations - of template functions receiving static stream buffer arguments in a - deduced context, the signature of the receiving function should use - @ref flat_static_buffer_base. - - When used with @ref flat_static_buffer this implements a dynamic - buffer using no memory allocations. + @ref flat_static_buffer; however, to reduce the number of template + instantiations, objects should be passed `flat_static_buffer_base&`. @see @ref flat_static_buffer */ @@ -44,22 +56,12 @@ class flat_static_buffer_base char* last_; char* end_; - flat_static_buffer_base(flat_static_buffer_base const& other) = delete; - flat_static_buffer_base& operator=(flat_static_buffer_base const&) = delete; + flat_static_buffer_base( + flat_static_buffer_base const& other) = delete; + flat_static_buffer_base& operator=( + flat_static_buffer_base const&) = delete; public: - /** The type used to represent the input sequence as a list of buffers. - - This buffer sequence is guaranteed to have length 1. - */ - using const_buffers_type = boost::asio::const_buffer; - - /** The type used to represent the output sequence as a list of buffers. - - This buffer sequence is guaranteed to have length 1. - */ - using mutable_buffers_type = boost::asio::mutable_buffer; - /** Constructor This creates a dynamic buffer using the provided storage area. @@ -68,71 +70,129 @@ public: @param n The number of valid bytes pointed to by `p`. */ - flat_static_buffer_base(void* p, std::size_t n) + flat_static_buffer_base( + void* p, std::size_t n) noexcept { - reset_impl(p, n); + reset(p, n); } - /// Return the size of the input sequence. + /// Change the number of readable and writable bytes to zero. + inline + void + clear() noexcept; + + // VFALCO Deprecate this + /// Change the number of readable and writable bytes to zero. + void + reset() noexcept + { + clear(); + } + + //-------------------------------------------------------------------------- + + /// The ConstBufferSequence used to represent the readable bytes. + using const_buffers_type = boost::asio::const_buffer; + + /// The MutableBufferSequence used to represent the readable bytes. + using mutable_data_type = boost::asio::mutable_buffer; + + /// The MutableBufferSequence used to represent the writable bytes. + using mutable_buffers_type = boost::asio::mutable_buffer; + + /// Returns the number of readable bytes. std::size_t - size() const + size() const noexcept { return out_ - in_; } - /// Return the maximum sum of the input and output sequence sizes. + /// Return the maximum number of bytes, both readable and writable, that can ever be held. std::size_t - max_size() const + max_size() const noexcept { return dist(begin_, end_); } - /// Return the maximum sum of input and output sizes that can be held without an allocation. + /// Return the maximum number of bytes, both readable and writable, that can be held without requiring an allocation. std::size_t - capacity() const + capacity() const noexcept { return max_size(); } - /** Get a list of buffers that represent the input sequence. - - @note These buffers remain valid across subsequent calls to `prepare`. - */ + /// Returns a constant buffer sequence representing the readable bytes const_buffers_type - data() const; + data() const noexcept + { + return {in_, dist(in_, out_)}; + } - /// Set the input and output sequences to size 0 - void - reset(); + /// Returns a constant buffer sequence representing the readable bytes + const_buffers_type + cdata() const noexcept + { + return data(); + } - /** Get a list of buffers that represent the output sequence, with the given size. + /// Returns a mutable buffer sequence representing the readable bytes + mutable_data_type + data() noexcept + { + return {in_, dist(in_, out_)}; + } - @throws std::length_error if the size would exceed the limit - imposed by the underlying mutable buffer sequence. + /** Returns a mutable buffer sequence representing writable bytes. + + Returns a mutable buffer sequence representing the writable + bytes containing exactly `n` bytes of storage. - @note Buffers representing the input sequence acquired prior to - this call remain valid. + All buffers sequences previously obtained using + @ref data or @ref prepare are invalidated. + + @param n The desired number of bytes in the returned buffer + sequence. + + @throws std::length_error if `size() + n` exceeds `max_size()`. */ + inline mutable_buffers_type prepare(std::size_t n); - /** Move bytes from the output sequence to the input sequence. + /** Append writable bytes to the readable bytes. - @note Buffers representing the input sequence acquired prior to - this call remain valid. + Appends n bytes from the start of the writable bytes to the + end of the readable bytes. The remainder of the writable bytes + are discarded. If n is greater than the number of writable + bytes, all writable bytes are appended to the readable bytes. + + All buffers sequences previously obtained using + @ref data or @ref prepare are invalidated. + + @param n The number of bytes to append. If this number + is greater than the number of writable bytes, all + writable bytes are appended. */ void - commit(std::size_t n) + commit(std::size_t n) noexcept { out_ += (std::min)(n, last_ - out_); } - /// Remove bytes from the input sequence. + /** Remove bytes from beginning of the readable bytes. + + Removes n bytes from the beginning of the readable bytes. + + All buffers sequences previously obtained using + @ref data or @ref prepare are invalidated. + + @param n The number of bytes to remove. If this number + is greater than the number of readable bytes, all + readable bytes are removed. + */ + inline void - consume(std::size_t n) - { - consume_impl(n); - } + consume(std::size_t n) noexcept; protected: /** Constructor @@ -154,33 +214,17 @@ protected: @param n The number of valid bytes pointed to by `p`. */ + inline void - reset(void* p, std::size_t n); + reset(void* p, std::size_t n) noexcept; private: static - inline std::size_t dist(char const* first, char const* last) { return static_cast(last - first); } - - template - void - reset_impl(); - - template - void - reset_impl(void* p, std::size_t n); - - template - mutable_buffers_type - prepare_impl(std::size_t n); - - template - void - consume_impl(std::size_t n); }; //------------------------------------------------------------------------------ @@ -251,4 +295,4 @@ public: #include -#endif +#endif \ No newline at end of file diff --git a/include/boost/beast/core/impl/flat_buffer.ipp b/include/boost/beast/core/impl/flat_buffer.ipp index afc843ac..252638d3 100644 --- a/include/boost/beast/core/impl/flat_buffer.ipp +++ b/include/boost/beast/core/impl/flat_buffer.ipp @@ -18,9 +18,11 @@ namespace boost { namespace beast { -/* Memory is laid out thusly: +/* Layout: - begin_ ..|.. in_ ..|.. out_ ..|.. last_ ..|.. end_ + begin_ in_ out_ last_ end_ + |<------->|<---------->|<---------->|<------->| + | readable | writable | */ template @@ -40,7 +42,8 @@ basic_flat_buffer() , out_(nullptr) , last_(nullptr) , end_(nullptr) - , max_((std::numeric_limits::max)()) + , max_((std::numeric_limits< + std::size_t>::max)()) { } @@ -59,20 +62,24 @@ basic_flat_buffer(std::size_t limit) template basic_flat_buffer:: basic_flat_buffer(Allocator const& alloc) - : boost::empty_value(boost::empty_init_t(), alloc) + : boost::empty_value( + boost::empty_init_t(), alloc) , begin_(nullptr) , in_(nullptr) , out_(nullptr) , last_(nullptr) , end_(nullptr) - , max_((std::numeric_limits::max)()) + , max_((std::numeric_limits< + std::size_t>::max)()) { } template basic_flat_buffer:: -basic_flat_buffer(std::size_t limit, Allocator const& alloc) - : boost::empty_value(boost::empty_init_t(), alloc) +basic_flat_buffer( + std::size_t limit, Allocator const& alloc) + : boost::empty_value( + boost::empty_init_t(), alloc) , begin_(nullptr) , in_(nullptr) , out_(nullptr) @@ -85,23 +92,23 @@ basic_flat_buffer(std::size_t limit, Allocator const& alloc) template basic_flat_buffer:: basic_flat_buffer(basic_flat_buffer&& other) - : boost::empty_value(boost::empty_init_t(), - std::move(other.get())) + : boost::empty_value( + boost::empty_init_t(), std::move(other.get())) , begin_(boost::exchange(other.begin_, nullptr)) , in_(boost::exchange(other.in_, nullptr)) , out_(boost::exchange(other.out_, nullptr)) - , last_(out_) + , last_(boost::exchange(other.last_, nullptr)) , end_(boost::exchange(other.end_, nullptr)) , max_(other.max_) { - other.last_ = nullptr; } template basic_flat_buffer:: -basic_flat_buffer(basic_flat_buffer&& other, - Allocator const& alloc) - : boost::empty_value(boost::empty_init_t(), alloc) +basic_flat_buffer( + basic_flat_buffer&& other, Allocator const& alloc) + : boost::empty_value( + boost::empty_init_t(), alloc) { if(this->get() != other.get()) { @@ -119,7 +126,7 @@ basic_flat_buffer(basic_flat_buffer&& other, begin_ = other.begin_; in_ = other.in_; out_ = other.out_; - last_ = out_; + last_ = other.out_; // invalidate end_ = other.end_; max_ = other.max_; other.begin_ = nullptr; @@ -229,6 +236,36 @@ operator=(basic_flat_buffer const& other) -> return *this; } +template +void +basic_flat_buffer:: +shrink_to_fit() +{ + auto const len = size(); + if(len == capacity()) + return; + char* p; + if(len > 0) + { + BOOST_ASSERT(begin_); + BOOST_ASSERT(in_); + p = alloc_traits::allocate( + this->get(), len); + std::memcpy(p, in_, len); + } + else + { + p = nullptr; + } + alloc_traits::deallocate( + this->get(), begin_, this->dist(begin_, end_)); + begin_ = p; + in_ = begin_; + out_ = begin_ + len; + last_ = out_; + end_ = out_; +} + //------------------------------------------------------------------------------ template @@ -284,7 +321,7 @@ prepare(std::size_t n) -> template void basic_flat_buffer:: -consume(std::size_t n) +consume(std::size_t n) noexcept { if(n >= dist(in_, out_)) { @@ -295,40 +332,9 @@ consume(std::size_t n) in_ += n; } -template -void -basic_flat_buffer:: -shrink_to_fit() -{ - auto const len = size(); - if(len == capacity()) - return; - char* p; - if(len > 0) - { - BOOST_ASSERT(begin_); - BOOST_ASSERT(in_); - p = alloc_traits::allocate( - this->get(), len); - std::memcpy(p, in_, len); - } - else - { - p = nullptr; - } - alloc_traits::deallocate( - this->get(), begin_, dist(begin_, end_)); - begin_ = p; - in_ = begin_; - out_ = begin_ + len; - last_ = out_; - end_ = out_; -} - //------------------------------------------------------------------------------ template -inline void basic_flat_buffer:: reset() @@ -339,20 +345,17 @@ reset() template template -inline void basic_flat_buffer:: copy_from(DynamicBuffer const& buffer) { if(buffer.size() == 0) return; - using boost::asio::buffer_copy; - commit(buffer_copy( + commit(boost::asio::buffer_copy( prepare(buffer.size()), buffer.data())); } template -inline void basic_flat_buffer:: move_assign(basic_flat_buffer& other, std::true_type) @@ -373,7 +376,6 @@ move_assign(basic_flat_buffer& other, std::true_type) } template -inline void basic_flat_buffer:: move_assign(basic_flat_buffer& other, std::false_type) @@ -391,7 +393,6 @@ move_assign(basic_flat_buffer& other, std::false_type) } template -inline void basic_flat_buffer:: copy_assign(basic_flat_buffer const& other, std::true_type) @@ -403,7 +404,6 @@ copy_assign(basic_flat_buffer const& other, std::true_type) } template -inline void basic_flat_buffer:: copy_assign(basic_flat_buffer const& other, std::false_type) @@ -414,7 +414,6 @@ copy_assign(basic_flat_buffer const& other, std::false_type) } template -inline void basic_flat_buffer:: swap(basic_flat_buffer& other) @@ -424,7 +423,6 @@ swap(basic_flat_buffer& other) } template -inline void basic_flat_buffer:: swap(basic_flat_buffer& other, std::true_type) @@ -441,7 +439,6 @@ swap(basic_flat_buffer& other, std::true_type) } template -inline void basic_flat_buffer:: swap(basic_flat_buffer& other, std::false_type) diff --git a/include/boost/beast/core/impl/flat_static_buffer.ipp b/include/boost/beast/core/impl/flat_static_buffer.ipp index 54bf2921..65767306 100644 --- a/include/boost/beast/core/impl/flat_static_buffer.ipp +++ b/include/boost/beast/core/impl/flat_static_buffer.ipp @@ -21,72 +21,26 @@ namespace boost { namespace beast { -/* Memory is laid out thusly: +/* Layout: - begin_ ..|.. in_ ..|.. out_ ..|.. last_ ..|.. end_ + begin_ in_ out_ last_ end_ + |<------->|<---------->|<---------->|<------->| + | readable | writable | */ -inline -auto -flat_static_buffer_base:: -data() const -> - const_buffers_type -{ - return {in_, dist(in_, out_)}; -} - -inline void flat_static_buffer_base:: -reset() +clear() noexcept { - reset_impl(); + in_ = begin_; + out_ = begin_; + last_ = begin_; } -inline auto flat_static_buffer_base:: prepare(std::size_t n) -> mutable_buffers_type -{ - return prepare_impl(n); -} - -inline -void -flat_static_buffer_base:: -reset(void* p, std::size_t n) -{ - reset_impl(p, n); -} - -template -void -flat_static_buffer_base:: -reset_impl() -{ - in_ = begin_; - out_ = begin_; - last_ = begin_; -} - -template -void -flat_static_buffer_base:: -reset_impl(void* p, std::size_t n) -{ - begin_ = static_cast(p); - in_ = begin_; - out_ = begin_; - last_ = begin_; - end_ = begin_ + n; -} - -template -auto -flat_static_buffer_base:: -prepare_impl(std::size_t n) -> - mutable_buffers_type { if(n <= dist(out_, end_)) { @@ -105,10 +59,9 @@ prepare_impl(std::size_t n) -> return {out_, n}; } -template void flat_static_buffer_base:: -consume_impl(std::size_t n) +consume(std::size_t n) noexcept { if(n >= size()) { @@ -119,6 +72,17 @@ consume_impl(std::size_t n) in_ += n; } +void +flat_static_buffer_base:: +reset(void* p, std::size_t n) noexcept +{ + begin_ = static_cast(p); + in_ = begin_; + out_ = begin_; + last_ = begin_; + end_ = begin_ + n; +} + //------------------------------------------------------------------------------ template @@ -147,4 +111,4 @@ operator=(flat_static_buffer const& other) -> } // beast } // boost -#endif +#endif \ No newline at end of file diff --git a/include/boost/beast/core/impl/multi_buffer.ipp b/include/boost/beast/core/impl/multi_buffer.ipp index fb48424a..0454c25d 100644 --- a/include/boost/beast/core/impl/multi_buffer.ipp +++ b/include/boost/beast/core/impl/multi_buffer.ipp @@ -18,6 +18,7 @@ #include #include #include +#include #include namespace boost { @@ -86,6 +87,7 @@ namespace beast { in_pos_ out_pos_ == 0 out_end_ == 0 */ +//------------------------------------------------------------------------------ template class basic_multi_buffer::element @@ -93,105 +95,99 @@ class basic_multi_buffer::element boost::intrusive::link_mode< boost::intrusive::normal_link>> { - using size_type = - typename detail::allocator_traits::size_type; + using size_type = typename + detail::allocator_traits::size_type; size_type const size_; public: element(element const&) = delete; - element& operator=(element const&) = delete; explicit - element(size_type n) + element(size_type n) noexcept : size_(n) { } size_type - size() const + size() const noexcept { return size_; } char* - data() const + data() const noexcept { return const_cast( reinterpret_cast(this + 1)); } }; +//------------------------------------------------------------------------------ + template -class basic_multi_buffer::const_buffers_type +template +class basic_multi_buffer::readable_bytes { basic_multi_buffer const* b_; friend class basic_multi_buffer; explicit - const_buffers_type(basic_multi_buffer const& b); + readable_bytes( + basic_multi_buffer const& b) noexcept + : b_(&b) + { + } public: - using value_type = boost::asio::const_buffer; + using value_type = typename std::conditional< + IsMutable, + boost::asio::mutable_buffer, + boost::asio::const_buffer>::type; class const_iterator; - const_buffers_type() = delete; - const_buffers_type(const_buffers_type const&) = default; - const_buffers_type& operator=(const_buffers_type const&) = default; + readable_bytes() = delete; + readable_bytes& operator=(readable_bytes const&) = default; - const_iterator - begin() const; + template< + bool OtherIsMutable, + bool ThisIsMutable = IsMutable> + readable_bytes( + readable_bytes const& other, + typename std::enable_if< + ! ThisIsMutable || OtherIsMutable>::type* = 0) + : b_(other.b_) + { + } - const_iterator - end() const; + const_iterator begin() const noexcept; + const_iterator end() const noexcept; friend std::size_t - buffer_size(const_buffers_type const& buffers) + buffer_size(readable_bytes const& buffers) noexcept { return buffers.b_->size(); } }; -template -class basic_multi_buffer::mutable_buffers_type -{ - basic_multi_buffer const* b_; - - friend class basic_multi_buffer; - - explicit - mutable_buffers_type(basic_multi_buffer const& b); - -public: - using value_type = mutable_buffer; - - class const_iterator; - - mutable_buffers_type() = delete; - mutable_buffers_type(mutable_buffers_type const&) = default; - mutable_buffers_type& operator=(mutable_buffers_type const&) = default; - - const_iterator - begin() const; - - const_iterator - end() const; -}; - //------------------------------------------------------------------------------ template -class basic_multi_buffer::const_buffers_type::const_iterator +template +class + basic_multi_buffer:: + readable_bytes:: + const_iterator { basic_multi_buffer const* b_ = nullptr; typename list_type::const_iterator it_; public: using value_type = - typename const_buffers_type::value_type; + typename readable_bytes::value_type; using pointer = value_type const*; using reference = value_type; using difference_type = std::ptrdiff_t; @@ -204,27 +200,28 @@ public: const_iterator& operator=(const_iterator&& other) = default; const_iterator& operator=(const_iterator const& other) = default; - const_iterator(basic_multi_buffer const& b, - typename list_type::const_iterator const& it) + const_iterator( + basic_multi_buffer const& b, + typename list_type::const_iterator const& it) noexcept : b_(&b) , it_(it) { } bool - operator==(const_iterator const& other) const + operator==(const_iterator const& other) const noexcept { return b_ == other.b_ && it_ == other.it_; } bool - operator!=(const_iterator const& other) const + operator!=(const_iterator const& other) const noexcept { return !(*this == other); } reference - operator*() const + operator*() const noexcept { auto const& e = *it_; return value_type{e.data(), @@ -237,14 +234,14 @@ public: operator->() const = delete; const_iterator& - operator++() + operator++() noexcept { ++it_; return *this; } const_iterator - operator++(int) + operator++(int) noexcept { auto temp = *this; ++(*this); @@ -252,14 +249,14 @@ public: } const_iterator& - operator--() + operator--() noexcept { --it_; return *this; } const_iterator - operator--(int) + operator--(int) noexcept { auto temp = *this; --(*this); @@ -267,36 +264,34 @@ public: } }; -template -basic_multi_buffer:: -const_buffers_type:: -const_buffers_type( - basic_multi_buffer const& b) - : b_(&b) -{ -} +//------------------------------------------------------------------------------ template -auto -basic_multi_buffer:: -const_buffers_type:: -begin() const -> - const_iterator +class basic_multi_buffer::mutable_buffers_type { - return const_iterator{*b_, b_->list_.begin()}; -} + basic_multi_buffer const* b_; -template -auto -basic_multi_buffer:: -const_buffers_type:: -end() const -> - const_iterator -{ - return const_iterator{*b_, b_->out_ == - b_->list_.end() ? b_->list_.end() : - std::next(b_->out_)}; -} + friend class basic_multi_buffer; + + explicit + mutable_buffers_type( + basic_multi_buffer const& b) noexcept + : b_(&b) + { + } + +public: + using value_type = mutable_buffer; + + class const_iterator; + + mutable_buffers_type() = delete; + mutable_buffers_type(mutable_buffers_type const&) = default; + mutable_buffers_type& operator=(mutable_buffers_type const&) = default; + + const_iterator begin() const noexcept; + const_iterator end() const noexcept; +}; //------------------------------------------------------------------------------ @@ -321,27 +316,28 @@ public: const_iterator& operator=(const_iterator&& other) = default; const_iterator& operator=(const_iterator const& other) = default; - const_iterator(basic_multi_buffer const& b, - typename list_type::const_iterator const& it) + const_iterator( + basic_multi_buffer const& b, + typename list_type::const_iterator const& it) noexcept : b_(&b) , it_(it) { } bool - operator==(const_iterator const& other) const + operator==(const_iterator const& other) const noexcept { return b_ == other.b_ && it_ == other.it_; } bool - operator!=(const_iterator const& other) const + operator!=(const_iterator const& other) const noexcept { return !(*this == other); } reference - operator*() const + operator*() const noexcept { auto const& e = *it_; return value_type{e.data(), @@ -354,14 +350,14 @@ public: operator->() const = delete; const_iterator& - operator++() + operator++() noexcept { ++it_; return *this; } const_iterator - operator++(int) + operator++(int) noexcept { auto temp = *this; ++(*this); @@ -369,14 +365,14 @@ public: } const_iterator& - operator--() + operator--() noexcept { --it_; return *this; } const_iterator - operator--(int) + operator--(int) noexcept { auto temp = *this; --(*this); @@ -384,20 +380,37 @@ public: } }; +//------------------------------------------------------------------------------ + template +template +auto basic_multi_buffer:: -mutable_buffers_type:: -mutable_buffers_type( - basic_multi_buffer const& b) - : b_(&b) +readable_bytes:: +begin() const noexcept -> + const_iterator { + return const_iterator{*b_, b_->list_.begin()}; +} + +template +template +auto +basic_multi_buffer:: +readable_bytes:: +end() const noexcept -> + const_iterator +{ + return const_iterator{*b_, b_->out_ == + b_->list_.end() ? b_->list_.end() : + std::next(b_->out_)}; } template auto basic_multi_buffer:: mutable_buffers_type:: -begin() const -> +begin() const noexcept -> const_iterator { return const_iterator{*b_, b_->out_}; @@ -407,7 +420,7 @@ template auto basic_multi_buffer:: mutable_buffers_type:: -end() const -> +end() const noexcept -> const_iterator { return const_iterator{*b_, b_->list_.end()}; @@ -424,22 +437,7 @@ basic_multi_buffer:: template basic_multi_buffer:: -basic_multi_buffer() - : out_(list_.end()) -{ -} - -template -basic_multi_buffer:: -basic_multi_buffer(std::size_t limit) - : max_(limit) - , out_(list_.end()) -{ -} - -template -basic_multi_buffer:: -basic_multi_buffer(Allocator const& alloc) +basic_multi_buffer(Allocator const& alloc) noexcept : boost::empty_value< base_alloc_type>(boost::empty_init_t(), alloc) , out_(list_.end()) @@ -449,7 +447,7 @@ basic_multi_buffer(Allocator const& alloc) template basic_multi_buffer:: basic_multi_buffer(std::size_t limit, - Allocator const& alloc) + Allocator const& alloc) noexcept : boost::empty_value< base_alloc_type>(boost::empty_init_t(), alloc) , max_(limit) @@ -459,7 +457,7 @@ basic_multi_buffer(std::size_t limit, template basic_multi_buffer:: -basic_multi_buffer(basic_multi_buffer&& other) +basic_multi_buffer(basic_multi_buffer&& other) noexcept : boost::empty_value< base_alloc_type>(boost::empty_init_t(), std::move(other.get())) , max_(other.max_) @@ -598,10 +596,12 @@ operator=( return *this; } +//------------------------------------------------------------------------------ + template std::size_t basic_multi_buffer:: -capacity() const +capacity() const noexcept { auto pos = out_; if(pos == list_.end()) @@ -615,12 +615,21 @@ capacity() const template auto basic_multi_buffer:: -data() const -> +data() const noexcept -> const_buffers_type { return const_buffers_type(*this); } +template +auto +basic_multi_buffer:: +data() noexcept -> + mutable_data_type +{ + return mutable_data_type(*this); +} + template auto basic_multi_buffer:: @@ -723,7 +732,7 @@ prepare(size_type n) -> template void basic_multi_buffer:: -commit(size_type n) +commit(size_type n) noexcept { if(list_.empty()) return; @@ -770,7 +779,7 @@ commit(size_type n) template void basic_multi_buffer:: -consume(size_type n) +consume(size_type n) noexcept { if(list_.empty()) return; @@ -836,10 +845,23 @@ consume(size_type n) } template -inline void basic_multi_buffer:: -delete_list() +reset() noexcept +{ + delete_list(); + list_.clear(); + out_ = list_.end(); + in_size_ = 0; + in_pos_ = 0; + out_pos_ = 0; + out_end_ = 0; +} + +template +void +basic_multi_buffer:: +delete_list() noexcept { for(auto iter = list_.begin(); iter != list_.end();) { @@ -851,24 +873,8 @@ delete_list() } } -template -inline -void -basic_multi_buffer:: -reset() -{ - delete_list(); - list_.clear(); - out_ = list_.end(); - in_size_ = 0; - in_pos_ = 0; - out_pos_ = 0; - out_end_ = 0; -} - template template -inline void basic_multi_buffer:: copy_from(DynamicBuffer const& buffer) @@ -881,7 +887,6 @@ copy_from(DynamicBuffer const& buffer) } template -inline void basic_multi_buffer:: move_assign(basic_multi_buffer& other, std::false_type) @@ -898,10 +903,9 @@ move_assign(basic_multi_buffer& other, std::false_type) } template -inline void basic_multi_buffer:: -move_assign(basic_multi_buffer& other, std::true_type) +move_assign(basic_multi_buffer& other, std::true_type) noexcept { this->get() = std::move(other.get()); auto const at_end = @@ -922,7 +926,6 @@ move_assign(basic_multi_buffer& other, std::true_type) } template -inline void basic_multi_buffer:: copy_assign( @@ -934,7 +937,6 @@ copy_assign( } template -inline void basic_multi_buffer:: copy_assign( @@ -947,20 +949,18 @@ copy_assign( } template -inline void basic_multi_buffer:: -swap(basic_multi_buffer& other) +swap(basic_multi_buffer& other) noexcept { swap(other, typename alloc_traits::propagate_on_container_swap{}); } template -inline void basic_multi_buffer:: -swap(basic_multi_buffer& other, std::true_type) +swap(basic_multi_buffer& other, std::true_type) noexcept { using std::swap; auto const at_end0 = @@ -981,10 +981,9 @@ swap(basic_multi_buffer& other, std::true_type) } template -inline void basic_multi_buffer:: -swap(basic_multi_buffer& other, std::false_type) +swap(basic_multi_buffer& other, std::false_type) noexcept { BOOST_ASSERT(this->get() == other.get()); using std::swap; @@ -1008,7 +1007,7 @@ template void swap( basic_multi_buffer& lhs, - basic_multi_buffer& rhs) + basic_multi_buffer& rhs) noexcept { lhs.swap(rhs); } diff --git a/include/boost/beast/core/impl/static_buffer.ipp b/include/boost/beast/core/impl/static_buffer.ipp index bd498c97..c860fde8 100644 --- a/include/boost/beast/core/impl/static_buffer.ipp +++ b/include/boost/beast/core/impl/static_buffer.ipp @@ -23,7 +23,7 @@ namespace beast { inline static_buffer_base:: -static_buffer_base(void* p, std::size_t size) +static_buffer_base(void* p, std::size_t size) noexcept : begin_(static_cast(p)) , capacity_(size) { @@ -32,7 +32,7 @@ static_buffer_base(void* p, std::size_t size) inline auto static_buffer_base:: -data() const -> +data() const noexcept -> const_buffers_type { using boost::asio::const_buffer; @@ -53,11 +53,11 @@ data() const -> inline auto static_buffer_base:: -mutable_data() -> - mutable_buffers_type +data() noexcept -> + mutable_data_type { using boost::asio::mutable_buffer; - mutable_buffers_type result; + mutable_data_type result; if(in_off_ + in_size_ <= capacity_) { result[0] = mutable_buffer{begin_ + in_off_, in_size_}; @@ -74,14 +74,14 @@ mutable_data() -> inline auto static_buffer_base:: -prepare(std::size_t size) -> +prepare(std::size_t n) -> mutable_buffers_type { using boost::asio::mutable_buffer; - if(size > capacity_ - in_size_) + if(n > capacity_ - in_size_) BOOST_THROW_EXCEPTION(std::length_error{ "buffer overflow"}); - out_size_ = size; + out_size_ = n; auto const out_off = (in_off_ + in_size_) % capacity_; mutable_buffers_type result; if(out_off + out_size_ <= capacity_ ) @@ -100,27 +100,27 @@ prepare(std::size_t size) -> inline void static_buffer_base:: -commit(std::size_t size) +commit(std::size_t n) noexcept { - in_size_ += (std::min)(size, out_size_); + in_size_ += (std::min)(n, out_size_); out_size_ = 0; } inline void static_buffer_base:: -consume(std::size_t size) +consume(std::size_t n) noexcept { - if(size < in_size_) + if(n < in_size_) { - in_off_ = (in_off_ + size) % capacity_; - in_size_ -= size; + in_off_ = (in_off_ + n) % capacity_; + in_size_ -= n; } else { // rewind the offset, so the next call to prepare // can have a longer contiguous segment. this helps - // algorithms optimized for larger buffesr. + // algorithms optimized for larger buffers. in_off_ = 0; in_size_ = 0; } @@ -129,10 +129,10 @@ consume(std::size_t size) inline void static_buffer_base:: -reset(void* p, std::size_t size) +reset(void* p, std::size_t n) noexcept { begin_ = static_cast(p); - capacity_ = size; + capacity_ = n; in_off_ = 0; in_size_ = 0; out_size_ = 0; diff --git a/include/boost/beast/core/multi_buffer.hpp b/include/boost/beast/core/multi_buffer.hpp index 27c43ac4..5cd85dfe 100644 --- a/include/boost/beast/core/multi_buffer.hpp +++ b/include/boost/beast/core/multi_buffer.hpp @@ -23,14 +23,39 @@ namespace boost { namespace beast { -/** A @b DynamicBuffer that uses multiple buffers internally. +/** A dynamic buffer providing sequences of variable length. - The implementation uses a sequence of one or more character arrays - of varying sizes. Additional character array objects are appended to - the sequence to accommodate changes in the size of the character - sequence. + A dynamic buffer encapsulates memory storage that may be + automatically resized as required, where the memory is + divided into two regions: readable bytes followed by + writable bytes. These memory regions are internal to + the dynamic buffer, but direct access to the elements + is provided to permit them to be efficiently used with + I/O operations. - @note Meets the requirements of @b DynamicBuffer. + The implementation uses a sequence of one or more byte + arrays of varying sizes to represent the readable and + writable bytes. Additional byte array objects are + appended to the sequence to accommodate changes in the + desired size. The behavior and implementation of this + container is most similar to `std::deque`. + + Objects of this type meet the requirements of @b DynamicBuffer + and have the following additional properties: + + @li The buffer sequence representing the readable bytes + returned by @ref data is mutable. + + @li Buffer sequences representing the readable and writable + bytes, returned by @ref data and @ref prepare, may have + length greater than one. + + @li A configurable maximum buffer size may be set upon + construction. Attempts to exceed the buffer size will throw + `std::length_error`. + + @li Sequences previously obtained using @ref data remain + valid after calls to @ref prepare or @ref commit. @tparam Allocator The allocator to use for managing memory. */ @@ -51,6 +76,9 @@ class basic_multi_buffer // contains `element` followed by raw storage bytes. class element; + template + class readable_bytes; + using alloc_traits = detail::allocator_traits; using list_type = typename boost::intrusive::make_list>::type; @@ -82,82 +110,103 @@ public: /// The type of allocator used. using allocator_type = Allocator; -#if BOOST_BEAST_DOXYGEN - /// The type used to represent the input sequence as a list of buffers. - using const_buffers_type = __implementation_defined__; - - /// The type used to represent the output sequence as a list of buffers. - using mutable_buffers_type = __implementation_defined__; - -#else - class const_buffers_type; - - class mutable_buffers_type; - -#endif - /// Destructor ~basic_multi_buffer(); /** Constructor - Upon construction, capacity will be zero. + Upon construction, @ref capacity will return zero. */ - basic_multi_buffer(); + basic_multi_buffer() noexcept( + std::is_nothrow_default_constructible::value) + : + out_(list_.end()) + { + } - /** Constructor. + /** Constructor + + Upon construction, @ref capacity will return zero. @param limit The setting for @ref max_size. */ explicit - basic_multi_buffer(std::size_t limit); + basic_multi_buffer(std::size_t limit) noexcept( + std::is_nothrow_default_constructible::value) + : max_(limit) + , out_(list_.end()) + { + } - /** Constructor. + /** Constructor + + Upon construction, @ref capacity will return zero. @param alloc The allocator to use. */ explicit - basic_multi_buffer(Allocator const& alloc); + basic_multi_buffer(Allocator const& alloc) noexcept; - /** Constructor. + /** Constructor + + Upon construction, @ref capacity will return zero. @param limit The setting for @ref max_size. @param alloc The allocator to use. */ basic_multi_buffer( - std::size_t limit, Allocator const& alloc); + std::size_t limit, Allocator const& alloc) noexcept; - /** Move constructor + /** Move Constructor - After the move, `*this` will have an empty output sequence. + Constructs the container with the contents of other + using move semantics. After the move, other is + guaranteed to be empty. + + Buffer sequences previously obtained using @ref data + or @ref prepare are not invalidated after the move. @param other The object to move from. After the move, - The object's state will be as if constructed using - its current allocator and limit. + the moved-from object's state will be as if default + constructed using its current allocator and limit. */ - basic_multi_buffer(basic_multi_buffer&& other); + basic_multi_buffer(basic_multi_buffer&& other) noexcept; - /** Move constructor + /** Move Constructor - After the move, `*this` will have an empty output sequence. + 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. + + All buffers sequences previously obtained using + @ref data or @ref prepare are invalidated. @param other The object to move from. After the move, - The object's state will be as if constructed using - its current allocator and limit. + the moved-from object's state will be as if default + constructed using its current allocator and limit. - @param alloc The allocator to use. + @param alloc The allocator to use for the newly + constructed object. */ basic_multi_buffer(basic_multi_buffer&& other, Allocator const& alloc); - /** Copy constructor. + /** Copy Constructor + + The newly constructed object will have a copy of the + allocator and contents of other, and zero writable bytes. @param other The object to copy from. */ basic_multi_buffer(basic_multi_buffer const& other); - /** Copy constructor + /** 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. @param other The object to copy from. @@ -166,7 +215,10 @@ public: basic_multi_buffer(basic_multi_buffer const& other, Allocator const& alloc); - /** Copy constructor. + /** Copy Constructor + + The newly constructed object will have a copy of the + contents of other, and zero writable bytes. @param other The object to copy from. */ @@ -174,7 +226,11 @@ public: basic_multi_buffer(basic_multi_buffer< OtherAlloc> const& other); - /** Copy constructor. + /** 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. @param other The object to copy from. @@ -184,20 +240,28 @@ public: basic_multi_buffer(basic_multi_buffer< OtherAlloc> const& other, allocator_type const& alloc); - /** Move assignment + /** Move Assignment - After the move, `*this` will have an empty output sequence. + 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. + + Buffer sequences previously obtained using @ref data + or @ref prepare are not invalidated after the move. @param other The object to move from. After the move, - The object's state will be as if constructed using - its current allocator and limit. + the moved-from object's state will be as if default + constructed using its current allocator and limit. */ basic_multi_buffer& operator=(basic_multi_buffer&& other); - /** Copy assignment + /** Copy Assignment - After the copy, `*this` will have an empty output sequence. + 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. @param other The object to copy from. */ @@ -205,7 +269,9 @@ public: /** Copy assignment - After the copy, `*this` will have an empty output sequence. + The assigned object will have a copy of the contents + of other, and zero writable bytes. The previous contents + of this container are deleted. @param other The object to copy from. */ @@ -213,102 +279,142 @@ public: basic_multi_buffer& operator=( basic_multi_buffer const& other); - /// Returns a copy of the associated allocator. + /// Returns a copy of the allocator used. allocator_type get_allocator() const { return this->get(); } - /// Returns the size of the input sequence. + //-------------------------------------------------------------------------- + +#if BOOST_BEAST_DOXYGEN + /// The ConstBufferSequence used to represent the readable bytes. + using const_buffers_type = __implementation_defined__; + + /// The MutableBufferSequence used to represent the readable bytes. + using mutable_data_type = __implementation_defined__; + + /// The MutableBufferSequence used to represent the writable bytes. + using mutable_buffers_type = __implementation_defined__; +#else + using const_buffers_type = readable_bytes; + using mutable_data_type = readable_bytes; + class mutable_buffers_type; +#endif + + /// Returns the number of readable bytes. size_type - size() const + size() const noexcept { return in_size_; } - /// Returns the permitted maximum sum of the sizes of the input and output sequence. + /// Return the maximum number of bytes, both readable and writable, that can ever be held. size_type - max_size() const + max_size() const noexcept { return max_; } - /// Returns the maximum sum of the sizes of the input sequence and output sequence the buffer can hold without requiring reallocation. + /// Return the maximum number of bytes, both readable and writable, that can be held without requiring an allocation. std::size_t - capacity() const; + capacity() const noexcept; - /** Get a list of buffers that represents the input sequence. - - @note These buffers remain valid across subsequent calls to `prepare`. - */ + /// Returns a constant buffer sequence representing the readable bytes const_buffers_type - data() const; + data() const noexcept; - /** Get a list of buffers that represents the output sequence, with the given size. + /// Returns a constant buffer sequence representing the readable bytes + const_buffers_type + cdata() const noexcept + { + return data(); + } - @note Buffers representing the input sequence acquired prior to - this call remain valid. + /** Returns a mutable buffer sequence representing the readable bytes. + + @note The sequence may contain zero or more contiguous memory + regions. + */ + mutable_data_type + data() noexcept; + + /** Returns a mutable buffer sequence representing writable bytes. + + Returns a mutable buffer sequence representing the writable + bytes containing exactly `n` bytes of storage. Memory may be + reallocated as needed. + + All buffer sequences previously obtained using @ref prepare are + invalidated. Buffer sequences previously obtained using @ref data + remain valid. + + @param n The desired number of bytes in the returned buffer + sequence. + + @throws std::length_error if `size() + n` exceeds `max_size()`. */ mutable_buffers_type prepare(size_type n); - /** Move bytes from the output sequence to the input sequence. + /** Append writable bytes to the readable bytes. - @note Buffers representing the input sequence acquired prior to - this call remain valid. + Appends n bytes from the start of the writable bytes to the + end of the readable bytes. The remainder of the writable bytes + are discarded. If n is greater than the number of writable + bytes, all writable bytes are appended to the readable bytes. + + All buffer sequences previously obtained using @ref prepare are + invalidated. Buffer sequences previously obtained using @ref data + remain valid. + + @param n The number of bytes to append. If this number + is greater than the number of writable bytes, all + writable bytes are appended. */ void - commit(size_type n); + commit(size_type n) noexcept; - /// Remove bytes from the input sequence. + /** Remove bytes from beginning of the readable bytes. + + Removes n bytes from the beginning of the readable bytes. + + All buffers sequences previously obtained using + @ref data or @ref prepare are invalidated. + + @param n The number of bytes to remove. If this number + is greater than the number of readable bytes, all + readable bytes are removed. + */ void - consume(size_type n); + consume(size_type n) noexcept; + /// Exchange two dynamic buffers template friend void swap( basic_multi_buffer& lhs, - basic_multi_buffer& rhs); + basic_multi_buffer& rhs) noexcept; private: template friend class basic_multi_buffer; - void - delete_list(); - - void - reset(); + void reset() noexcept; + void delete_list() noexcept; template - void - copy_from(DynamicBuffer const& other); - - void - move_assign(basic_multi_buffer& other, std::false_type); - - void - move_assign(basic_multi_buffer& other, std::true_type); - - void - copy_assign(basic_multi_buffer const& other, std::false_type); - - void - copy_assign(basic_multi_buffer const& other, std::true_type); - - void - swap(basic_multi_buffer&); - - void - swap(basic_multi_buffer&, std::true_type); - - void - swap(basic_multi_buffer&, std::false_type); - - void - debug_check() const; + void copy_from(DynamicBuffer const& other); + void move_assign(basic_multi_buffer& other, std::false_type); + void move_assign(basic_multi_buffer& other, std::true_type) noexcept; + void copy_assign(basic_multi_buffer const& other, std::false_type); + void copy_assign(basic_multi_buffer const& other, std::true_type); + void swap(basic_multi_buffer&) noexcept; + void swap(basic_multi_buffer&, std::true_type) noexcept; + void swap(basic_multi_buffer&, std::false_type) noexcept; + void debug_check() const; }; /// A typical multi buffer @@ -319,4 +425,4 @@ using multi_buffer = basic_multi_buffer>; #include -#endif +#endif \ No newline at end of file diff --git a/include/boost/beast/core/static_buffer.hpp b/include/boost/beast/core/static_buffer.hpp index fa59638b..c74be62e 100644 --- a/include/boost/beast/core/static_buffer.hpp +++ b/include/boost/beast/core/static_buffer.hpp @@ -12,6 +12,7 @@ #include #include +#include #include #include #include @@ -20,21 +21,34 @@ namespace boost { namespace beast { -/** A circular @b DynamicBuffer with a fixed size internal buffer. +/** A dynamic buffer providing a fixed, circular buffer. - This implements a circular dynamic buffer. Calls to @ref prepare - never require moving memory. The buffer sequences returned may - be up to length two. - Ownership of the underlying storage belongs to the derived class. + A dynamic buffer encapsulates memory storage that may be + automatically resized as required, where the memory is + divided into two regions: readable bytes followed by + writable bytes. These memory regions are internal to + the dynamic buffer, but direct access to the elements + is provided to permit them to be efficiently used with + I/O operations. + + Objects of this type meet the requirements of @b DynamicBuffer + and have the following additional properties: + + @li A mutable buffer sequence representing the readable + bytes is returned by @ref data when `this` is non-const. + + @li Buffer sequences representing the readable and writable + bytes, returned by @ref data and @ref prepare, will have + length at most one. + + @li All operations execute in constant time. + + @li Ownership of the underlying storage belongs to the + derived class. @note Variables are usually declared using the template class - @ref static_buffer; however, to reduce the number of instantiations - of template functions receiving static stream buffer arguments in a - deduced context, the signature of the receiving function should use - @ref static_buffer_base. - - When used with @ref static_buffer this implements a dynamic - buffer using no memory allocations. + @ref static_buffer; however, to reduce the number of template + instantiations, objects should be passed `static_buffer_base&`. @see @ref static_buffer */ @@ -46,18 +60,98 @@ class static_buffer_base std::size_t out_size_ = 0; std::size_t capacity_; + class const_buffer_pair; + + class mutable_buffer_pair + { + boost::asio::mutable_buffer b_[2]; + + friend class const_buffer_pair; + + public: + using const_iterator = + boost::asio::mutable_buffer const*; + + // workaround for buffers_iterator bug + using value_type = + boost::asio::mutable_buffer; + + mutable_buffer_pair() = default; + mutable_buffer_pair( + mutable_buffer_pair const&) = default; + + boost::asio::mutable_buffer& + operator[](int i) noexcept + { + BOOST_ASSERT(i >= 0 && i < 2); + return b_[i]; + } + + const_iterator + begin() const noexcept + { + return &b_[0]; + } + + const_iterator + end() const noexcept + { + if(b_[1].size() > 0) + return &b_[2]; + else + return &b_[1]; + } + }; + + class const_buffer_pair + { + boost::asio::const_buffer b_[2]; + + public: + using const_iterator = + boost::asio::const_buffer const*; + + // workaround for buffers_iterator bug + using value_type = + boost::asio::const_buffer; + + const_buffer_pair() = default; + const_buffer_pair( + const_buffer_pair const&) = default; + + const_buffer_pair( + mutable_buffer_pair const& other) + : b_{other.b_[0], other.b_[1]} + { + } + + boost::asio::const_buffer& + operator[](int i) noexcept + { + BOOST_ASSERT(i >= 0 && i < 2); + return b_[i]; + } + + const_iterator + begin() const noexcept + { + return &b_[0]; + } + + const_iterator + end() const noexcept + { + if(b_[1].size() > 0) + return &b_[2]; + else + return &b_[1]; + } + }; + static_buffer_base(static_buffer_base const& other) = delete; static_buffer_base& operator=(static_buffer_base const&) = delete; public: - /// The type used to represent the input sequence as a list of buffers. - using const_buffers_type = - std::array; - - /// The type used to represent the output sequence as a list of buffers. - using mutable_buffers_type = - std::array; - /** Constructor This creates a dynamic buffer using the provided storage area. @@ -66,65 +160,108 @@ public: @param size The number of valid bytes pointed to by `p`. */ - static_buffer_base(void* p, std::size_t size); + static_buffer_base(void* p, std::size_t size) noexcept; - /// Return the size of the input sequence. + //-------------------------------------------------------------------------- + +#if BOOST_BEAST_DOXYGEN + /// The ConstBufferSequence used to represent the readable bytes. + using const_buffers_type = __implementation_defined__; + + /// The MutableBufferSequence used to represent the readable bytes. + using mutable_data_type = __implementation_defined__; + + /// The MutableBufferSequence used to represent the writable bytes. + using mutable_buffers_type = __implementation_defined__; +#else + using const_buffers_type = const_buffer_pair; + using mutable_data_type = mutable_buffer_pair; + using mutable_buffers_type = mutable_buffer_pair; +#endif + + /// Returns the number of readable bytes. std::size_t - size() const + size() const noexcept { return in_size_; } - /// Return the maximum sum of the input and output sequence sizes. + /// Return the maximum number of bytes, both readable and writable, that can ever be held. std::size_t - max_size() const + max_size() const noexcept { return capacity_; } - /// Return the maximum sum of input and output sizes that can be held without an allocation. + /// Return the maximum number of bytes, both readable and writable, that can be held without requiring an allocation. std::size_t - capacity() const + capacity() const noexcept { return capacity_; } - /** Get a list of buffers that represent the input sequence. - */ + /// Returns a constant buffer sequence representing the readable bytes const_buffers_type - data() const; + data() const noexcept; + + /// Returns a constant buffer sequence representing the readable bytes + const_buffers_type + cdata() const noexcept + { + return data(); + } - /** Get a mutable list of buffers that represent the input sequence. + /// Returns a mutable buffer sequence representing the readable bytes + mutable_data_type + data() noexcept; + + /** Returns a mutable buffer sequence representing writable bytes. + + Returns a mutable buffer sequence representing the writable + bytes containing exactly `n` bytes of storage. Memory may be + reallocated as needed. + + All buffers sequences previously obtained using + @ref data or @ref prepare are invalidated. + + @param n The desired number of bytes in the returned buffer + sequence. + + @throws std::length_error if `size() + n` exceeds `max_size()`. */ mutable_buffers_type - mutable_data(); + prepare(std::size_t n); - /** Get a list of buffers that represent the output sequence, with the given size. + /** Append writable bytes to the readable bytes. - @param size The number of bytes to request. + Appends n bytes from the start of the writable bytes to the + end of the readable bytes. The remainder of the writable bytes + are discarded. If n is greater than the number of writable + bytes, all writable bytes are appended to the readable bytes. - @throws std::length_error if the size would exceed the capacity. - */ - mutable_buffers_type - prepare(std::size_t size); + All buffers sequences previously obtained using + @ref data or @ref prepare are invalidated. - /** Move bytes from the output sequence to the input sequence. - - @param size The number of bytes to commit. If this is greater - than the size of the output sequence, the entire output - sequence is committed. + @param n The number of bytes to append. If this number + is greater than the number of writable bytes, all + writable bytes are appended. */ void - commit(std::size_t size); + commit(std::size_t n) noexcept; - /** Remove bytes from the input sequence. + /** Remove bytes from beginning of the readable bytes. - @param size The number of bytes to consume. If this is greater - than the size of the input sequence, the entire input sequence - is consumed. + Removes n bytes from the beginning of the readable bytes. + + All buffers sequences previously obtained using + @ref data or @ref prepare are invalidated. + + @param n The number of bytes to remove. If this number + is greater than the number of readable bytes, all + readable bytes are removed. */ void - consume(std::size_t size); + consume(std::size_t n) noexcept; protected: /** Constructor @@ -147,7 +284,7 @@ protected: @param size The number of valid bytes pointed to by `p`. */ void - reset(void* p, std::size_t size); + reset(void* p, std::size_t size) noexcept; }; //------------------------------------------------------------------------------ diff --git a/include/boost/beast/websocket/impl/close.ipp b/include/boost/beast/websocket/impl/close.ipp index 113504d6..834fa229 100644 --- a/include/boost/beast/websocket/impl/close.ipp +++ b/include/boost/beast/websocket/impl/close.ipp @@ -236,7 +236,7 @@ operator()( d.ws.rd_close_ = true; auto const mb = buffers_prefix( clamp(d.ws.rd_fh_.len), - d.ws.rd_buf_.mutable_data()); + d.ws.rd_buf_.data()); if(d.ws.rd_fh_.len > 0 && d.ws.rd_fh_.mask) detail::mask_inplace(mb, d.ws.rd_key_); detail::read_close(d.ws.cr_, mb, d.ev); @@ -381,7 +381,7 @@ close(close_reason const& cr, error_code& ec) rd_close_ = true; auto const mb = buffers_prefix( clamp(rd_fh_.len), - rd_buf_.mutable_data()); + rd_buf_.data()); if(rd_fh_.len > 0 && rd_fh_.mask) detail::mask_inplace(mb, rd_key_); detail::read_close(cr_, mb, result); diff --git a/include/boost/beast/websocket/impl/read.ipp b/include/boost/beast/websocket/impl/read.ipp index 5cbe97c1..6aa547db 100644 --- a/include/boost/beast/websocket/impl/read.ipp +++ b/include/boost/beast/websocket/impl/read.ipp @@ -267,7 +267,7 @@ operator()( if(ws_.rd_fh_.len > 0 && ws_.rd_fh_.mask) detail::mask_inplace(buffers_prefix( clamp(ws_.rd_fh_.len), - ws_.rd_buf_.mutable_data()), + ws_.rd_buf_.data()), ws_.rd_key_); if(detail::is_control(ws_.rd_fh_.op)) { @@ -454,14 +454,14 @@ operator()( ws_.rd_buf_.commit(bytes_transferred); if(ws_.rd_fh_.mask) detail::mask_inplace(buffers_prefix(clamp( - ws_.rd_remain_), ws_.rd_buf_.mutable_data()), + ws_.rd_remain_), ws_.rd_buf_.data()), ws_.rd_key_); } if(ws_.rd_buf_.size() > 0) { // Copy from the read buffer. // The mask was already applied. - bytes_transferred = buffer_copy(cb_, + bytes_transferred = boost::asio::buffer_copy(cb_, ws_.rd_buf_.data(), clamp(ws_.rd_remain_)); auto const mb = buffers_prefix( bytes_transferred, cb_); @@ -542,7 +542,7 @@ operator()( if(ws_.rd_fh_.mask) detail::mask_inplace( buffers_prefix(clamp(ws_.rd_remain_), - ws_.rd_buf_.mutable_data()), ws_.rd_key_); + ws_.rd_buf_.data()), ws_.rd_key_); did_read_ = true; } zlib::z_params zs; @@ -1058,7 +1058,7 @@ loop: // of the buffer holding payload data. if(rd_fh_.len > 0 && rd_fh_.mask) detail::mask_inplace(buffers_prefix( - clamp(rd_fh_.len), rd_buf_.mutable_data()), + clamp(rd_fh_.len), rd_buf_.data()), rd_key_); if(detail::is_control(rd_fh_.op)) { @@ -1160,15 +1160,15 @@ loop: if(rd_fh_.mask) detail::mask_inplace( buffers_prefix(clamp(rd_remain_), - rd_buf_.mutable_data()), rd_key_); + rd_buf_.data()), rd_key_); } if(rd_buf_.size() > 0) { // Copy from the read buffer. // The mask was already applied. auto const bytes_transferred = - buffer_copy(buffers, rd_buf_.data(), - clamp(rd_remain_)); + boost::asio::buffer_copy(buffers, + rd_buf_.data(), clamp(rd_remain_)); auto const mb = buffers_prefix( bytes_transferred, buffers); rd_remain_ -= bytes_transferred; @@ -1267,7 +1267,7 @@ loop: if(rd_fh_.mask) detail::mask_inplace( buffers_prefix(clamp(rd_remain_), - rd_buf_.mutable_data()), rd_key_); + rd_buf_.data()), rd_key_); auto const in = buffers_prefix( clamp(rd_remain_), buffers_front( rd_buf_.data())); diff --git a/test/beast/core/flat_buffer.cpp b/test/beast/core/flat_buffer.cpp index 458acc22..719f9b55 100644 --- a/test/beast/core/flat_buffer.cpp +++ b/test/beast/core/flat_buffer.cpp @@ -17,17 +17,63 @@ #include #include #include +#include #include +#include namespace boost { namespace beast { -BOOST_STATIC_ASSERT( - boost::asio::is_dynamic_buffer::value); - class flat_buffer_test : public beast::unit_test::suite { public: + BOOST_STATIC_ASSERT( + boost::asio::is_dynamic_buffer< + flat_buffer>::value); + BOOST_STATIC_ASSERT( + boost::asio::is_const_buffer_sequence< + flat_buffer::const_buffers_type>::value); + BOOST_STATIC_ASSERT( + boost::asio::is_mutable_buffer_sequence< + flat_buffer::mutable_data_type>::value); + BOOST_STATIC_ASSERT( + boost::asio::is_mutable_buffer_sequence< + flat_buffer::mutable_buffers_type>::value); + BOOST_STATIC_ASSERT(std::is_convertible< + flat_buffer::mutable_data_type, + flat_buffer::const_buffers_type>::value); + + template + void + testMutableData() + { + DynamicBuffer b; + DynamicBuffer const& cb = b; + ostream(b) << "Hello"; + BOOST_STATIC_ASSERT( + boost::asio::is_const_buffer_sequence< + decltype(cb.data())>::value && + ! boost::asio::is_mutable_buffer_sequence< + decltype(cb.data())>::value); + BOOST_STATIC_ASSERT( + boost::asio::is_const_buffer_sequence< + decltype(cb.cdata())>::value && + ! boost::asio::is_mutable_buffer_sequence< + decltype(cb.cdata())>::value); + BOOST_STATIC_ASSERT( + boost::asio::is_mutable_buffer_sequence< + decltype(b.data())>::value); + std::for_each( + boost::asio::buffers_iterator::begin(b.data()), + boost::asio::buffers_iterator::end(b.data()), + [](char& c) + { + c = static_cast(std::toupper(c)); + }); + BEAST_EXPECT(buffers_to_string(b.data()) == "HELLO"); + BEAST_EXPECT(buffers_to_string(b.cdata()) == "HELLO"); + } + void testBuffer() { @@ -338,7 +384,6 @@ public: BEAST_EXPECT(b.capacity() >= 125); b.shrink_to_fit(); BEAST_EXPECT(b.capacity() == b.size()); - } } @@ -346,6 +391,7 @@ public: run() override { testBuffer(); + testMutableData(); } }; diff --git a/test/beast/core/flat_static_buffer.cpp b/test/beast/core/flat_static_buffer.cpp index f094c213..e9162b5e 100644 --- a/test/beast/core/flat_static_buffer.cpp +++ b/test/beast/core/flat_static_buffer.cpp @@ -15,17 +15,64 @@ #include #include #include +#include +#include +#include #include namespace boost { namespace beast { -BOOST_STATIC_ASSERT( - boost::asio::is_dynamic_buffer::value); - class flat_static_buffer_test : public beast::unit_test::suite { public: + BOOST_STATIC_ASSERT( + boost::asio::is_dynamic_buffer< + flat_static_buffer_base>::value); + BOOST_STATIC_ASSERT( + boost::asio::is_const_buffer_sequence< + flat_static_buffer_base::const_buffers_type>::value); + BOOST_STATIC_ASSERT( + boost::asio::is_mutable_buffer_sequence< + flat_static_buffer_base::mutable_data_type>::value); + BOOST_STATIC_ASSERT( + boost::asio::is_mutable_buffer_sequence< + flat_static_buffer_base::mutable_buffers_type>::value); + BOOST_STATIC_ASSERT(std::is_convertible< + flat_static_buffer_base::mutable_data_type, + flat_static_buffer_base::const_buffers_type>::value); + + template + void + testMutableData() + { + DynamicBuffer b; + DynamicBuffer const& cb = b; + ostream(b) << "Hello"; + BOOST_STATIC_ASSERT( + boost::asio::is_const_buffer_sequence< + decltype(cb.data())>::value && + ! boost::asio::is_mutable_buffer_sequence< + decltype(cb.data())>::value); + BOOST_STATIC_ASSERT( + boost::asio::is_const_buffer_sequence< + decltype(cb.cdata())>::value && + ! boost::asio::is_mutable_buffer_sequence< + decltype(cb.cdata())>::value); + BOOST_STATIC_ASSERT( + boost::asio::is_mutable_buffer_sequence< + decltype(b.data())>::value); + std::for_each( + boost::asio::buffers_iterator::begin(b.data()), + boost::asio::buffers_iterator::end(b.data()), + [](char& c) + { + c = static_cast(std::toupper(c)); + }); + BEAST_EXPECT(buffers_to_string(b.data()) == "HELLO"); + BEAST_EXPECT(buffers_to_string(b.cdata()) == "HELLO"); + } + void testStaticBuffer() { @@ -226,6 +273,7 @@ public: { testBuffer(); testStaticBuffer(); + testMutableData>(); } }; diff --git a/test/beast/core/multi_buffer.cpp b/test/beast/core/multi_buffer.cpp index 448fa33a..ae44042f 100644 --- a/test/beast/core/multi_buffer.cpp +++ b/test/beast/core/multi_buffer.cpp @@ -18,20 +18,35 @@ #include #include #include +#include #include #include +#include #include #include namespace boost { namespace beast { -BOOST_STATIC_ASSERT( - boost::asio::is_dynamic_buffer::value); - class multi_buffer_test : public beast::unit_test::suite { public: + BOOST_STATIC_ASSERT( + boost::asio::is_dynamic_buffer< + multi_buffer>::value); + BOOST_STATIC_ASSERT( + boost::asio::is_const_buffer_sequence< + multi_buffer::const_buffers_type>::value); + BOOST_STATIC_ASSERT( + boost::asio::is_mutable_buffer_sequence< + multi_buffer::mutable_data_type>::value); + BOOST_STATIC_ASSERT( + boost::asio::is_mutable_buffer_sequence< + multi_buffer::mutable_buffers_type>::value); + BOOST_STATIC_ASSERT(std::is_convertible< + multi_buffer::mutable_data_type, + multi_buffer::const_buffers_type>::value); + template static bool @@ -60,6 +75,38 @@ public: u = std::forward(v); } + template + void + testMutableData() + { + DynamicBuffer b; + DynamicBuffer const& cb = b; + ostream(b) << "Hello"; + BOOST_STATIC_ASSERT( + boost::asio::is_const_buffer_sequence< + decltype(cb.data())>::value && + ! boost::asio::is_mutable_buffer_sequence< + decltype(cb.data())>::value); + BOOST_STATIC_ASSERT( + boost::asio::is_const_buffer_sequence< + decltype(cb.cdata())>::value && + ! boost::asio::is_mutable_buffer_sequence< + decltype(cb.cdata())>::value); + BOOST_STATIC_ASSERT( + boost::asio::is_mutable_buffer_sequence< + decltype(b.data())>::value); + + std::for_each( + boost::asio::buffers_iterator::begin(b.data()), + boost::asio::buffers_iterator::end(b.data()), + [](char& c) + { + c = static_cast(std::toupper(c)); + }); + BEAST_EXPECT(buffers_to_string(b.data()) == "HELLO"); + BEAST_EXPECT(buffers_to_string(b.cdata()) == "HELLO"); + } + void testMatrix1() { @@ -588,6 +635,7 @@ public: testMatrix2(); testIterators(); testMembers(); + testMutableData(); } }; diff --git a/test/beast/core/static_buffer.cpp b/test/beast/core/static_buffer.cpp index 292e469e..1a26c168 100644 --- a/test/beast/core/static_buffer.cpp +++ b/test/beast/core/static_buffer.cpp @@ -15,6 +15,9 @@ #include #include #include +#include +#include +#include #include namespace boost { @@ -26,6 +29,53 @@ BOOST_STATIC_ASSERT( class static_buffer_test : public beast::unit_test::suite { public: + BOOST_STATIC_ASSERT( + boost::asio::is_dynamic_buffer< + static_buffer_base>::value); + BOOST_STATIC_ASSERT( + boost::asio::is_const_buffer_sequence< + static_buffer_base::const_buffers_type>::value); + BOOST_STATIC_ASSERT( + boost::asio::is_mutable_buffer_sequence< + static_buffer_base::mutable_data_type>::value); + BOOST_STATIC_ASSERT( + boost::asio::is_mutable_buffer_sequence< + static_buffer_base::mutable_buffers_type>::value); + BOOST_STATIC_ASSERT(std::is_convertible< + static_buffer_base::mutable_data_type, + static_buffer_base::const_buffers_type>::value); + + template + void + testMutableData() + { + DynamicBuffer b; + DynamicBuffer const& cb = b; + ostream(b) << "Hello"; + BOOST_STATIC_ASSERT( + boost::asio::is_const_buffer_sequence< + decltype(cb.data())>::value && + ! boost::asio::is_mutable_buffer_sequence< + decltype(cb.data())>::value); + BOOST_STATIC_ASSERT( + boost::asio::is_const_buffer_sequence< + decltype(cb.cdata())>::value && + ! boost::asio::is_mutable_buffer_sequence< + decltype(cb.cdata())>::value); + BOOST_STATIC_ASSERT( + boost::asio::is_mutable_buffer_sequence< + decltype(b.data())>::value); + std::for_each( + boost::asio::buffers_iterator::begin(b.data()), + boost::asio::buffers_iterator::end(b.data()), + [](char& c) + { + c = static_cast(std::toupper(c)); + }); + BEAST_EXPECT(buffers_to_string(b.data()) == "HELLO"); + BEAST_EXPECT(buffers_to_string(b.cdata()) == "HELLO"); + } + void testStaticBuffer() { @@ -225,7 +275,8 @@ public: void run() override { testBuffer(); - //testStaticBuffer(); + testStaticBuffer(); + testMutableData>(); } };