Dynamic buffers improvements:

fix #1305

Applies to:

    flat_buffer
    flat_static_buffer
    multi_buffer
    static_buffer

Changes:

* Revised all javadocs
* Move construction does not invalidate in some cases
* non-const data() returns a mutable buffer sequence
* Add cdata() to also return constant readable bytes
* Eligible member functions are declared noexcept
This commit is contained in:
Vinnie Falco
2018-11-29 12:57:29 -08:00
parent eb588ff82c
commit 29cf6ce61f
15 changed files with 1165 additions and 657 deletions

View File

@ -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
--------------------------------------------------------------------------------

View File

@ -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<base_alloc_type>;
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<OtherAlloc> 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<OtherAlloc> 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<OtherAlloc> 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<class Alloc>
friend
void
swap(
basic_flat_buffer<Alloc>& lhs,
basic_flat_buffer<Alloc>& rhs);
basic_flat_buffer<Alloc>&,
basic_flat_buffer<Alloc>&);
//--------------------------------------------------------------------------
/// 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<class DynamicBuffer>
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<std::allocator<char>>;

View File

@ -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<std::size_t>)(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<std::size_t>(last - first);
}
template<class = void>
void
reset_impl();
template<class = void>
void
reset_impl(void* p, std::size_t n);
template<class = void>
mutable_buffers_type
prepare_impl(std::size_t n);
template<class = void>
void
consume_impl(std::size_t n);
};
//------------------------------------------------------------------------------
@ -251,4 +295,4 @@ public:
#include <boost/beast/core/impl/flat_static_buffer.ipp>
#endif
#endif

View File

@ -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<class Allocator>
@ -40,7 +42,8 @@ basic_flat_buffer()
, out_(nullptr)
, last_(nullptr)
, end_(nullptr)
, max_((std::numeric_limits<std::size_t>::max)())
, max_((std::numeric_limits<
std::size_t>::max)())
{
}
@ -59,20 +62,24 @@ basic_flat_buffer(std::size_t limit)
template<class Allocator>
basic_flat_buffer<Allocator>::
basic_flat_buffer(Allocator const& alloc)
: boost::empty_value<base_alloc_type>(boost::empty_init_t(), alloc)
: boost::empty_value<base_alloc_type>(
boost::empty_init_t(), alloc)
, begin_(nullptr)
, in_(nullptr)
, out_(nullptr)
, last_(nullptr)
, end_(nullptr)
, max_((std::numeric_limits<std::size_t>::max)())
, max_((std::numeric_limits<
std::size_t>::max)())
{
}
template<class Allocator>
basic_flat_buffer<Allocator>::
basic_flat_buffer(std::size_t limit, Allocator const& alloc)
: boost::empty_value<base_alloc_type>(boost::empty_init_t(), alloc)
basic_flat_buffer(
std::size_t limit, Allocator const& alloc)
: boost::empty_value<base_alloc_type>(
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<class Allocator>
basic_flat_buffer<Allocator>::
basic_flat_buffer(basic_flat_buffer&& other)
: boost::empty_value<base_alloc_type>(boost::empty_init_t(),
std::move(other.get()))
: boost::empty_value<base_alloc_type>(
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<class Allocator>
basic_flat_buffer<Allocator>::
basic_flat_buffer(basic_flat_buffer&& other,
Allocator const& alloc)
: boost::empty_value<base_alloc_type>(boost::empty_init_t(), alloc)
basic_flat_buffer(
basic_flat_buffer&& other, Allocator const& alloc)
: boost::empty_value<base_alloc_type>(
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<OtherAlloc> const& other) ->
return *this;
}
template<class Allocator>
void
basic_flat_buffer<Allocator>::
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<class Allocator>
@ -284,7 +321,7 @@ prepare(std::size_t n) ->
template<class Allocator>
void
basic_flat_buffer<Allocator>::
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<class Allocator>
void
basic_flat_buffer<Allocator>::
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<class Allocator>
inline
void
basic_flat_buffer<Allocator>::
reset()
@ -339,20 +345,17 @@ reset()
template<class Allocator>
template<class DynamicBuffer>
inline
void
basic_flat_buffer<Allocator>::
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<class Allocator>
inline
void
basic_flat_buffer<Allocator>::
move_assign(basic_flat_buffer& other, std::true_type)
@ -373,7 +376,6 @@ move_assign(basic_flat_buffer& other, std::true_type)
}
template<class Allocator>
inline
void
basic_flat_buffer<Allocator>::
move_assign(basic_flat_buffer& other, std::false_type)
@ -391,7 +393,6 @@ move_assign(basic_flat_buffer& other, std::false_type)
}
template<class Allocator>
inline
void
basic_flat_buffer<Allocator>::
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<class Allocator>
inline
void
basic_flat_buffer<Allocator>::
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<class Allocator>
inline
void
basic_flat_buffer<Allocator>::
swap(basic_flat_buffer& other)
@ -424,7 +423,6 @@ swap(basic_flat_buffer& other)
}
template<class Allocator>
inline
void
basic_flat_buffer<Allocator>::
swap(basic_flat_buffer& other, std::true_type)
@ -441,7 +439,6 @@ swap(basic_flat_buffer& other, std::true_type)
}
template<class Allocator>
inline
void
basic_flat_buffer<Allocator>::
swap(basic_flat_buffer& other, std::false_type)

View File

@ -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<class>
void
flat_static_buffer_base::
reset_impl()
{
in_ = begin_;
out_ = begin_;
last_ = begin_;
}
template<class>
void
flat_static_buffer_base::
reset_impl(void* p, std::size_t n)
{
begin_ = static_cast<char*>(p);
in_ = begin_;
out_ = begin_;
last_ = begin_;
end_ = begin_ + n;
}
template<class>
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<class>
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<char*>(p);
in_ = begin_;
out_ = begin_;
last_ = begin_;
end_ = begin_ + n;
}
//------------------------------------------------------------------------------
template<std::size_t N>
@ -147,4 +111,4 @@ operator=(flat_static_buffer const& other) ->
} // beast
} // boost
#endif
#endif

View File

@ -18,6 +18,7 @@
#include <exception>
#include <sstream>
#include <string>
#include <type_traits>
#include <utility>
namespace boost {
@ -86,6 +87,7 @@ namespace beast {
in_pos_ out_pos_ == 0
out_end_ == 0
*/
//------------------------------------------------------------------------------
template<class Allocator>
class basic_multi_buffer<Allocator>::element
@ -93,105 +95,99 @@ class basic_multi_buffer<Allocator>::element
boost::intrusive::link_mode<
boost::intrusive::normal_link>>
{
using size_type =
typename detail::allocator_traits<Allocator>::size_type;
using size_type = typename
detail::allocator_traits<Allocator>::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<char*>(
reinterpret_cast<char const*>(this + 1));
}
};
//------------------------------------------------------------------------------
template<class Allocator>
class basic_multi_buffer<Allocator>::const_buffers_type
template<bool IsMutable>
class basic_multi_buffer<Allocator>::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<OtherIsMutable> 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 Allocator>
class basic_multi_buffer<Allocator>::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 Allocator>
class basic_multi_buffer<Allocator>::const_buffers_type::const_iterator
template<bool IsMutable>
class
basic_multi_buffer<Allocator>::
readable_bytes<IsMutable>::
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<class Allocator>
basic_multi_buffer<Allocator>::
const_buffers_type::
const_buffers_type(
basic_multi_buffer const& b)
: b_(&b)
{
}
//------------------------------------------------------------------------------
template<class Allocator>
auto
basic_multi_buffer<Allocator>::
const_buffers_type::
begin() const ->
const_iterator
class basic_multi_buffer<Allocator>::mutable_buffers_type
{
return const_iterator{*b_, b_->list_.begin()};
}
basic_multi_buffer const* b_;
template<class Allocator>
auto
basic_multi_buffer<Allocator>::
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<class Allocator>
template<bool IsMutable>
auto
basic_multi_buffer<Allocator>::
mutable_buffers_type::
mutable_buffers_type(
basic_multi_buffer const& b)
: b_(&b)
readable_bytes<IsMutable>::
begin() const noexcept ->
const_iterator
{
return const_iterator{*b_, b_->list_.begin()};
}
template<class Allocator>
template<bool IsMutable>
auto
basic_multi_buffer<Allocator>::
readable_bytes<IsMutable>::
end() const noexcept ->
const_iterator
{
return const_iterator{*b_, b_->out_ ==
b_->list_.end() ? b_->list_.end() :
std::next(b_->out_)};
}
template<class Allocator>
auto
basic_multi_buffer<Allocator>::
mutable_buffers_type::
begin() const ->
begin() const noexcept ->
const_iterator
{
return const_iterator{*b_, b_->out_};
@ -407,7 +420,7 @@ template<class Allocator>
auto
basic_multi_buffer<Allocator>::
mutable_buffers_type::
end() const ->
end() const noexcept ->
const_iterator
{
return const_iterator{*b_, b_->list_.end()};
@ -424,22 +437,7 @@ basic_multi_buffer<Allocator>::
template<class Allocator>
basic_multi_buffer<Allocator>::
basic_multi_buffer()
: out_(list_.end())
{
}
template<class Allocator>
basic_multi_buffer<Allocator>::
basic_multi_buffer(std::size_t limit)
: max_(limit)
, out_(list_.end())
{
}
template<class Allocator>
basic_multi_buffer<Allocator>::
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<class Allocator>
basic_multi_buffer<Allocator>::
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<class Allocator>
basic_multi_buffer<Allocator>::
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<class Allocator>
std::size_t
basic_multi_buffer<Allocator>::
capacity() const
capacity() const noexcept
{
auto pos = out_;
if(pos == list_.end())
@ -615,12 +615,21 @@ capacity() const
template<class Allocator>
auto
basic_multi_buffer<Allocator>::
data() const ->
data() const noexcept ->
const_buffers_type
{
return const_buffers_type(*this);
}
template<class Allocator>
auto
basic_multi_buffer<Allocator>::
data() noexcept ->
mutable_data_type
{
return mutable_data_type(*this);
}
template<class Allocator>
auto
basic_multi_buffer<Allocator>::
@ -723,7 +732,7 @@ prepare(size_type n) ->
template<class Allocator>
void
basic_multi_buffer<Allocator>::
commit(size_type n)
commit(size_type n) noexcept
{
if(list_.empty())
return;
@ -770,7 +779,7 @@ commit(size_type n)
template<class Allocator>
void
basic_multi_buffer<Allocator>::
consume(size_type n)
consume(size_type n) noexcept
{
if(list_.empty())
return;
@ -836,10 +845,23 @@ consume(size_type n)
}
template<class Allocator>
inline
void
basic_multi_buffer<Allocator>::
delete_list()
reset() noexcept
{
delete_list();
list_.clear();
out_ = list_.end();
in_size_ = 0;
in_pos_ = 0;
out_pos_ = 0;
out_end_ = 0;
}
template<class Allocator>
void
basic_multi_buffer<Allocator>::
delete_list() noexcept
{
for(auto iter = list_.begin(); iter != list_.end();)
{
@ -851,24 +873,8 @@ delete_list()
}
}
template<class Allocator>
inline
void
basic_multi_buffer<Allocator>::
reset()
{
delete_list();
list_.clear();
out_ = list_.end();
in_size_ = 0;
in_pos_ = 0;
out_pos_ = 0;
out_end_ = 0;
}
template<class Allocator>
template<class DynamicBuffer>
inline
void
basic_multi_buffer<Allocator>::
copy_from(DynamicBuffer const& buffer)
@ -881,7 +887,6 @@ copy_from(DynamicBuffer const& buffer)
}
template<class Allocator>
inline
void
basic_multi_buffer<Allocator>::
move_assign(basic_multi_buffer& other, std::false_type)
@ -898,10 +903,9 @@ move_assign(basic_multi_buffer& other, std::false_type)
}
template<class Allocator>
inline
void
basic_multi_buffer<Allocator>::
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<class Allocator>
inline
void
basic_multi_buffer<Allocator>::
copy_assign(
@ -934,7 +937,6 @@ copy_assign(
}
template<class Allocator>
inline
void
basic_multi_buffer<Allocator>::
copy_assign(
@ -947,20 +949,18 @@ copy_assign(
}
template<class Allocator>
inline
void
basic_multi_buffer<Allocator>::
swap(basic_multi_buffer& other)
swap(basic_multi_buffer& other) noexcept
{
swap(other, typename
alloc_traits::propagate_on_container_swap{});
}
template<class Allocator>
inline
void
basic_multi_buffer<Allocator>::
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<class Allocator>
inline
void
basic_multi_buffer<Allocator>::
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<class Allocator>
void
swap(
basic_multi_buffer<Allocator>& lhs,
basic_multi_buffer<Allocator>& rhs)
basic_multi_buffer<Allocator>& rhs) noexcept
{
lhs.swap(rhs);
}

View File

@ -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<char*>(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<char*>(p);
capacity_ = size;
capacity_ = n;
in_off_ = 0;
in_size_ = 0;
out_size_ = 0;

View File

@ -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<bool IsMutable>
class readable_bytes;
using alloc_traits = detail::allocator_traits<base_alloc_type>;
using list_type = typename boost::intrusive::make_list<element,
boost::intrusive::constant_time_size<true>>::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<Allocator>::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<Allocator>::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<OtherAlloc> 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<false>;
using mutable_data_type = readable_bytes<true>;
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<class Alloc>
friend
void
swap(
basic_multi_buffer<Alloc>& lhs,
basic_multi_buffer<Alloc>& rhs);
basic_multi_buffer<Alloc>& rhs) noexcept;
private:
template<class OtherAlloc>
friend class basic_multi_buffer;
void
delete_list();
void
reset();
void reset() noexcept;
void delete_list() noexcept;
template<class DynamicBuffer>
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<std::allocator<char>>;
#include <boost/beast/core/impl/multi_buffer.ipp>
#endif
#endif

View File

@ -12,6 +12,7 @@
#include <boost/beast/core/detail/config.hpp>
#include <boost/asio/buffer.hpp>
#include <boost/assert.hpp>
#include <algorithm>
#include <array>
#include <cstddef>
@ -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<boost::asio::const_buffer, 2>;
/// The type used to represent the output sequence as a list of buffers.
using mutable_buffers_type =
std::array<boost::asio::mutable_buffer, 2>;
/** 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;
};
//------------------------------------------------------------------------------

View File

@ -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);

View File

@ -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()));

View File

@ -17,17 +17,63 @@
#include <boost/beast/core/string.hpp>
#include <boost/beast/test/test_allocator.hpp>
#include <boost/beast/_experimental/unit_test/suite.hpp>
#include <boost/asio/buffers_iterator.hpp>
#include <algorithm>
#include <cctype>
namespace boost {
namespace beast {
BOOST_STATIC_ASSERT(
boost::asio::is_dynamic_buffer<flat_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<class DynamicBuffer>
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<decltype(b.data())>::begin(b.data()),
boost::asio::buffers_iterator<decltype(b.data())>::end(b.data()),
[](char& c)
{
c = static_cast<char>(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<flat_buffer>();
}
};

View File

@ -15,17 +15,64 @@
#include <boost/beast/core/ostream.hpp>
#include <boost/beast/core/string.hpp>
#include <boost/beast/_experimental/unit_test/suite.hpp>
#include <boost/asio/buffers_iterator.hpp>
#include <algorithm>
#include <cctype>
#include <string>
namespace boost {
namespace beast {
BOOST_STATIC_ASSERT(
boost::asio::is_dynamic_buffer<flat_static_buffer_base>::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<class DynamicBuffer>
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<decltype(b.data())>::begin(b.data()),
boost::asio::buffers_iterator<decltype(b.data())>::end(b.data()),
[](char& c)
{
c = static_cast<char>(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<flat_static_buffer<32>>();
}
};

View File

@ -18,20 +18,35 @@
#include <boost/beast/test/test_allocator.hpp>
#include <boost/beast/_experimental/unit_test/suite.hpp>
#include <boost/asio/buffer.hpp>
#include <boost/asio/buffers_iterator.hpp>
#include <algorithm>
#include <atomic>
#include <cctype>
#include <memory>
#include <string>
namespace boost {
namespace beast {
BOOST_STATIC_ASSERT(
boost::asio::is_dynamic_buffer<multi_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<class Alloc1, class Alloc2>
static
bool
@ -60,6 +75,38 @@ public:
u = std::forward<V>(v);
}
template<class DynamicBuffer>
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<decltype(b.data())>::begin(b.data()),
boost::asio::buffers_iterator<decltype(b.data())>::end(b.data()),
[](char& c)
{
c = static_cast<char>(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<multi_buffer>();
}
};

View File

@ -15,6 +15,9 @@
#include <boost/beast/core/ostream.hpp>
#include <boost/beast/core/string.hpp>
#include <boost/beast/_experimental/unit_test/suite.hpp>
#include <boost/asio/buffers_iterator.hpp>
#include <algorithm>
#include <cctype>
#include <string>
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<class DynamicBuffer>
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<decltype(b.data())>::begin(b.data()),
boost::asio::buffers_iterator<decltype(b.data())>::end(b.data()),
[](char& c)
{
c = static_cast<char>(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<static_buffer<32>>();
}
};