[boost] Re: Review Request: cyclic_buffer

2003-06-13 Thread Nigel Stewart
 I'm afraid I'm asking somebody else to do work here
though.  I've got a resizing circular buffer (Metrowerks::cdeque has 
shipped with CodeWarrior for years), but I'm not at liberty to share the 
source.  
I'm busy too, on my agenda is a 2-dimensional circular buffer
concept - which led me to revisit boost, and start lurking
on gmane.comp.lib.boost.devel.  I think if Jan goes ahead
with non-autoresizing circular_buffer, I might use that
as a basis for my 2-dimensional variant.  (A 2d sliding
window caching kind of problem)  With Jan's permission/blessing
obviously  Perhaps better not to infringe on Metrowerks
unless absolutely no consensus can be formed by patient
debate... :-)
The conceptual process here has been a big help for me
to crystalise some aspects of circular buffers, I genuinely
think that circular_buffer and circular_deque would both
be worthy additions to boost, once refined.

___
Unsubscribe  other changes: http://lists.boost.org/mailman/listinfo.cgi/boost


[boost] Re: Review Request: cyclic_buffer

2003-06-12 Thread Nigel Stewart
deque is more expensive than a resizing circular buffer in both 
performance and code size.  One also can not control *when* deque will 
allocate as one can with a resizing circular buffer.  In a nutshell, a 
resizing circular buffer is often a better deque than std::deque is. 
;-(  A resizing circular buffer is absolutely awesome when plugged into 
std::queue.  If your queue on average doesn't constantly grow, a 
resizing circular buffer is efficient, predictable and safe.  A 
std::deque in std::queue will likely continually rellocate buffers as it 
drops one off one end and adds one to the other (and at annoyingly 
unpredictable times if you're doing real-time).
	Howard,

I can see the appeal here in using a circular buffer in
this manner, while at the same time I'm cooling against
the compromise of insert auto-resizes, while push doesn't
which seems inconsistant.  Given that we both seem to have
a real-time orientation, we're probably not so far apart
in our thinking.  Perhaps we simply need to make a distinction
between a circular_buffer and a circular_deque which are only
different in terms of resizing semantics.  Each provides different
characteristics, and stand independently as boost containers.
Code re-use by itself doesn't seem to be a good enough reason
to mix the concepts and overload the namespace unnecessarily.
One idea that came to my mind was to have forward insertion,
backward insertion to resolve the question of semantics
of arbitrary insertion into a circular_buffer.  insert()
pushes rightmost items forwards, overwriting leftmost items
as necessary.  rinsert() pushes leftmost items backwards,
overwriting rightmost items as necessary.  Likewise for the
circular_deque which would reallocate as necessary to
accomodate any kind of insertion - items would never be
implicitly lost by a circular_deque.
	Nigel

___
Unsubscribe  other changes: http://lists.boost.org/mailman/listinfo.cgi/boost


Re: [boost] Re: Review Request: cyclic_buffer

2003-06-12 Thread Howard Hinnant
On Thursday, June 12, 2003, at 12:43  PM, Nigel Stewart wrote:

I can see the appeal here in using a circular buffer in
this manner, while at the same time I'm cooling against
the compromise of insert auto-resizes, while push doesn't
which seems inconsistant.  Given that we both seem to have
a real-time orientation, we're probably not so far apart
in our thinking.  Perhaps we simply need to make a distinction
between a circular_buffer and a circular_deque which are only
different in terms of resizing semantics.  Each provides different
characteristics, and stand independently as boost containers.
Code re-use by itself doesn't seem to be a good enough reason
to mix the concepts and overload the namespace unnecessarily.
I'm beginning to think you're correct.  An informed process would be to 
create both containers, and adapt both types to the other, and measure 
the results.  I'm afraid I'm asking somebody else to do work here 
though.  I've got a resizing circular buffer (Metrowerks::cdeque has 
shipped with CodeWarrior for years), but I'm not at liberty to share 
the source.  And my schedule is tight enough that even higher priority 
things are slipping right now, so I can't donate cycles to a 
non-resizing variant to compare to.

One idea that came to my mind was to have forward insertion,
backward insertion to resolve the question of semantics
of arbitrary insertion into a circular_buffer.  insert()
pushes rightmost items forwards, overwriting leftmost items
as necessary.  rinsert() pushes leftmost items backwards,
overwriting rightmost items as necessary.  Likewise for the
circular_deque which would reallocate as necessary to
accomodate any kind of insertion - items would never be
implicitly lost by a circular_deque.
This sounds very interesting for the non-resizing version.  I think for 
the resizing version, the insert should simply minimize the elements 
that it is going to move.  Either way, all pointers and iterators must 
be considered invalidated (unlike the non-resizing variant with insert 
and rinsert).  In my mind, when you insert into the middle of a 
resizing circular buffer, the behavior (mental model) is very 
deque-like, except of course for the ability to predict whether it will 
require a reallocation or not.

-Howard

___
Unsubscribe  other changes: http://lists.boost.org/mailman/listinfo.cgi/boost


Re: [boost] Re: Review Request: cyclic_buffer

2003-06-11 Thread Jan Gaspar
Hi all Cyclics!

I want to summarize what we have till now. What should be changed in the proposed
cyclic_buffer.

- Rename to circular_buffer.

- Add push_front() and pop_front().

- resize() to behave similarly to vector::resize().

- change_capacity() becomes again change_capacity(). I think the name change_capacity
best reflects what the method really does. The vector::reserve() can only increase the
capacity, never decrease - which may be confusing. And at last the circular_buffer is
not vector so it can have different methods from vector.

- insert() will always increase the size and possibly can increase the capacity (if not
sufficient).

Regards,

Jan



___
Unsubscribe  other changes: http://lists.boost.org/mailman/listinfo.cgi/boost


[boost] Re: Review Request: cyclic_buffer

2003-06-11 Thread Nigel Stewart
Hey, I'm keen on a circular argument...  :-)

- Rename to circular_buffer.
	Agree.

- Add push_front() and pop_front().

Agree.
- resize() to behave similarly to vector::resize().
Agree.  That means items will be lost from the
right end, if necessary.  Or, capacity will be
increased, if necessary.
- change_capacity() becomes again change_capacity(). I think the name change_capacity
best reflects what the method really does. The vector::reserve() can only increase the
capacity, never decrease - which may be confusing. And at last the circular_buffer is
not vector so it can have different methods from vector.
	set_capacity()?

Agree.  Make it clear in the documentation about
how change_capacity()/set_capacity() is different to reserve() because of
the possibility of data being removed to fit the specified
capacity.  A std::vector::reserve call is a hint, while
a circular_buffer::change_capacity/set_capacity is a hard limit.
- insert() will always increase the size and possibly can increase the capacity (if not
sufficient).
Agree.  We should document that circularity (and related constant-time
guarantees) applies to push/pop, but not insert. (or erase?)
This strikes me as a good compromise.  For one thing, it leaves the door
open to inserting in a manner that resizes the capacity.  (Except for the
problem that if the buffer is full, every insert will require O(n) time)
	---

The thread in relation to automatic resizing has not yet settled,
but I would suggest going ahead and doing a rev while that one
settles down.
	---

If you think there is a meaningful way that I can help you out over the
weekend, let me know.  (Linux, Cygwin, MSVC 6, 7, Solaris, etc...)
	Regards,

	Nigel Stewart

___
Unsubscribe  other changes: http://lists.boost.org/mailman/listinfo.cgi/boost


[boost] Re: Review Request: cyclic_buffer

2003-06-11 Thread Nigel Stewart

This strikes me as a good compromise.  For one thing, it leaves the 
door open to inserting in a manner that resizes the capacity.  (Except 
for the problem that if the buffer is full, every insert will require O(n) 
time)
I later realised an important point: insert will be O(n)
anyway, given the need to make room for the inserted items.
So, it seems that allowing automatic resizing in this
case does not cost anything in terms of performence
implications.
___
Unsubscribe  other changes: http://lists.boost.org/mailman/listinfo.cgi/boost


RE: [boost] Re: Review Request: cyclic_buffer

2003-06-11 Thread Paul A. Bristow
| -Original Message-
| From: [EMAIL PROTECTED]
| [mailto:[EMAIL PROTECTED] Behalf Of Nigel Stewart
| Sent: Wednesday, June 11, 2003 2:03 AM
| To: [EMAIL PROTECTED]
| Subject: [boost] Re: Review Request: cyclic_buffer
|
|   The generally accepted concept of a circular buffer
|   is a fixed size contiguous memory buffer.  Following
|   the principle of least surprise a circular_buffer
|   should not decide to resize itself.

On the Keep It Simple Sir principle, I agree with this.

Indeed, I think that most uses specifically require a fixed at construction
size,
and I suspect the code will be smaller, faster and correcter if this is the
specification.

Paul

Paul A Bristow, Prizet Farmhouse, Kendal, Cumbria, LA8 8AB  UK
+44 1539 561830   Mobile +44 7714 33 02 04
Mobile mailto:[EMAIL PROTECTED]
mailto:[EMAIL PROTECTED]

___
Unsubscribe  other changes: http://lists.boost.org/mailman/listinfo.cgi/boost


Re: [boost] Re: Review Request: cyclic_buffer

2003-06-11 Thread Howard Hinnant
On Wednesday, June 11, 2003, at 05:55  PM, Dave Gomboc wrote:

Why is
a deque inadequate?
deque is more expensive than a resizing circular buffer in both 
performance and code size.  One also can not control *when* deque will 
allocate as one can with a resizing circular buffer.  In a nutshell, a 
resizing circular buffer is often a better deque than std::deque is. 
;-(  A resizing circular buffer is absolutely awesome when plugged into 
std::queue.  If your queue on average doesn't constantly grow, a 
resizing circular buffer is efficient, predictable and safe.  A 
std::deque in std::queue will likely continually rellocate buffers as 
it drops one off one end and adds one to the other (and at annoyingly 
unpredictable times if you're doing real-time).

-Howard

___
Unsubscribe  other changes: http://lists.boost.org/mailman/listinfo.cgi/boost


Re: [boost] Re: Review Request: cyclic_buffer

2003-06-10 Thread Jan Gaspar
Hi Howard!

I like your idea. But (referring to the Nigel Stewart's posting) what would you
propose for the insert? Should I not provide it?

Jan

Howard Hinnant wrote:

 In article [EMAIL PROTECTED], Alisdair Meredith
 [EMAIL PROTECTED] wrote:

 | The one true circular buffer template is a nigh impossible goal,
 | because it means so many things to different people.
 snip
 | I would certainly like the documentation to explain the tradeoffs made
 | in the implementation, and why this particular variation was chosen as
 | most appropriate for the general case.

 Indeed!  Excellent post!

 Perhaps container adaptors could be applied to a sufficiently
 generalized circular buffer.

 What is the one true queue?  There isn't one.  It is best a container
 adaptor (policy based if you prefer).  I suspect that a more general
 circular buffer, restricted by various adaptors, might serve more uses
 with less code.

 The circular buffer I've needed (and coded) fits none of aforementioned
 descriptions.  But it is a circular buffer nonetheless.

 For my money, the general circular buffer is one that exists in a
 contiguous array, but with an arbitrary begin and end within that
 array, capable of wrapping around the end of the physical memory:

 +---+---+---+---+---+---+---+---+---+---+
 | 4 | 5 |   |   |   |   | 0 | 1 | 2 | 3 |
 +---+---+---+---+---+---+---+---+---+---+
   ^   ^
   |   |
  endbegin

 Constant time push/pop_front, constant time push/pop_back.  When begin
 and end collide, reallocation automatically happens vector-like.

 This data structure can be efficiently coded with a 4-word data
 structure.  For example:  data_ptr, capacity, size, begin_ptr  (there
 are other variations that also only take 4 words).

 From this you can write adaptors to constrain capacity, have push_back
 overwrite front(), throw an exception on size() exceeding capacity(),
 or whatever else you need to do.

 You can't adapt any other std::container to do this job because vector
 is the only one with capacity, but vector doesn't have a constant time
 push/pop_front.  But I believe you can adapt the above described
 container to meet the needs of other circular buffer requirements.
 For example:

 template class T, class Container = general_cyclic_bufferT 
 class my_cyclic_buffer
 {
 public:
// typedefs ...

my_cyclic_buffer(size_type cap) {c.reserve(cap);}

reference operator[](size_type n) {return c[n];}
// etc.

void push_back(const value_type x)
{
   if (capacity() != 0)
   {
  if (c.size() == c.capacity())
 c.pop_front();
  c.push_back(x);
   }
}

change_capacity(size_type new_cap)
{
   if (new_cap  c.capacity())
  c.reserve(new_cap);
   else if (new_cap  c.capacity())
   {
  c.erase(c.begin(), c.begin() + c.size() - new_cap);
  Container tmp(c);  // assumes tmp.capacity() == c.size()
  c.swap(tmp);
   }
}
// etc.

 private:
Container c;
 };

 --
 Howard Hinnant
 Metrowerks

 ___
 Unsubscribe  other changes: http://lists.boost.org/mailman/listinfo.cgi/boost

--
Jan Gaspar | [EMAIL PROTECTED]
Whitestein Technologies | www.whitestein.com
Panenska 28 | SK-81103 Bratislava | Slovak Republic
Tel +421(2)5930-0734 | Fax +421(2)5443-5512


___
Unsubscribe  other changes: http://lists.boost.org/mailman/listinfo.cgi/boost


Re: [boost] Re: Review Request: cyclic_buffer

2003-06-10 Thread Howard Hinnant
For the general container I propose that insert reallocates if size() 
would exceed capacity (invalidating all iterators, pointers and 
references).  And if size() does not exceed capacity, all iterators, 
pointers and references are still invalidated, unless the insert 
happens at one end or the other, in which case no pointers or 
references are invalidated (iterators still are invalidated).  I would 
expect a quality implementation to minimize the number of elements that 
need to be shifted.

insert in general should have the basic exception safety guarantee.  
But when inserting only one element at either end, the strong guarantee 
is in place.  Also if the value_type's copy constructor and assignment 
operator do not throw, then the strong guarantee is in place.

For any given adaptor, I think you could restrict or eliminate insert 
as appropriate for that model.

-Howard

On Tuesday, June 10, 2003, at 05:20  AM, Jan Gaspar wrote:

Hi Howard!

I like your idea. But (referring to the Nigel Stewart's posting) what 
would you
propose for the insert? Should I not provide it?

Jan

Howard Hinnant wrote:

In article [EMAIL PROTECTED], Alisdair Meredith
[EMAIL PROTECTED] wrote:
| The one true circular buffer template is a nigh impossible goal,
| because it means so many things to different people.
snip
| I would certainly like the documentation to explain the tradeoffs 
made
| in the implementation, and why this particular variation was chosen 
as
| most appropriate for the general case.

Indeed!  Excellent post!

Perhaps container adaptors could be applied to a sufficiently
generalized circular buffer.
What is the one true queue?  There isn't one.  It is best a container
adaptor (policy based if you prefer).  I suspect that a more general
circular buffer, restricted by various adaptors, might serve more uses
with less code.
The circular buffer I've needed (and coded) fits none of 
aforementioned
descriptions.  But it is a circular buffer nonetheless.

For my money, the general circular buffer is one that exists in a
contiguous array, but with an arbitrary begin and end within that
array, capable of wrapping around the end of the physical memory:
+---+---+---+---+---+---+---+---+---+---+
| 4 | 5 |   |   |   |   | 0 | 1 | 2 | 3 |
+---+---+---+---+---+---+---+---+---+---+
  ^   ^
  |   |
 endbegin
Constant time push/pop_front, constant time push/pop_back.  When begin
and end collide, reallocation automatically happens vector-like.
This data structure can be efficiently coded with a 4-word data
structure.  For example:  data_ptr, capacity, size, begin_ptr  (there
are other variations that also only take 4 words).
From this you can write adaptors to constrain capacity, have 
push_back
overwrite front(), throw an exception on size() exceeding capacity(),
or whatever else you need to do.
You can't adapt any other std::container to do this job because vector
is the only one with capacity, but vector doesn't have a constant time
push/pop_front.  But I believe you can adapt the above described
container to meet the needs of other circular buffer requirements.
For example:
template class T, class Container = general_cyclic_bufferT 
class my_cyclic_buffer
{
public:
   // typedefs ...
   my_cyclic_buffer(size_type cap) {c.reserve(cap);}

   reference operator[](size_type n) {return c[n];}
   // etc.
   void push_back(const value_type x)
   {
  if (capacity() != 0)
  {
 if (c.size() == c.capacity())
c.pop_front();
 c.push_back(x);
  }
   }
   change_capacity(size_type new_cap)
   {
  if (new_cap  c.capacity())
 c.reserve(new_cap);
  else if (new_cap  c.capacity())
  {
 c.erase(c.begin(), c.begin() + c.size() - new_cap);
 Container tmp(c);  // assumes tmp.capacity() == c.size()
 c.swap(tmp);
  }
   }
   // etc.
private:
   Container c;
};
--
Howard Hinnant
Metrowerks
___
Unsubscribe  other changes: 
http://lists.boost.org/mailman/listinfo.cgi/boost
--
Jan Gaspar | [EMAIL PROTECTED]
Whitestein Technologies | www.whitestein.com
Panenska 28 | SK-81103 Bratislava | Slovak Republic
Tel +421(2)5930-0734 | Fax +421(2)5443-5512
___
Unsubscribe  other changes: 
http://lists.boost.org/mailman/listinfo.cgi/boost

___
Unsubscribe  other changes: http://lists.boost.org/mailman/listinfo.cgi/boost


[boost] Re: Review Request: cyclic_buffer

2003-06-10 Thread Nigel Stewart

Constant time push/pop_front, constant time push/pop_back.  When begin
and end collide, reallocation automatically happens vector-like.
	Another point to consider:

Constant time push/pop can not be combined with std::vector
automatic re-allocation due to the linear cost of copying
into the realloc'd buffer.  Given that circular buffers
are often used in the context of real-time applications,
it seems to be consistant that push/pop from each end
should be truely constant time, and as a consequence the
the capacity is never implicitly changed unless explicitly
requested via reserve() or resize().
	So as a proposed design criteria:

	circular_buffer

- provides guaranteed O(1) push/pop
- provides O(n) arbitrary insertion
- provides O(n) manual capacity and size management
	in contrast to vector

- provides (amortized) O(1) push_back/pop_back
  O(n) in reallocation case
- provides O(n) arbitrary insertion
- provides O(n) manual capacity and size management

(The implication for real-time uses of circular_buffer is that
 only push and pop are allowed in time-critical code.)
This I think is a resounding case against re-allocation,
and reflected in the broader understanding of what circular
buffers are commonly used for.
A re-allocating circular_buffer should therefore only be
an adaptor or variant, but can not provide proper O(1)
push/pop, so is missing this critical feature of a true
circular_buffer.




___
Unsubscribe  other changes: http://lists.boost.org/mailman/listinfo.cgi/boost


Re: [boost] Re: Review Request: cyclic_buffer

2003-06-10 Thread Howard Hinnant
On Tuesday, June 10, 2003, at 10:48  AM, Jan Gaspar wrote:

insert in general should have the basic exception safety guarantee.
But when inserting only one element at either end, the strong 
guarantee
is in place.
I think the strong guarantee can reached only in case the 
cyclic_buffer is not
full (if there is no reallocation).
You can do it with push_back/front too regardless.  vector::push_back 
has the strong guarantee.  If you're using copy semantics (which I 
would expect instead of move semantics), you basically have to build a 
whole new vector in a temp (with the push_back), and then swap it in 
when everything is set (just describing the realloc branch here).

If you're looking ahead to use move semantics to perform this 
operation, it is a little more tricky, but not too much.  In fact it is 
easier with cyclic_buffer than it is with vector! :-)  You have to 
push_back the new element into the temporary first.  If it throws, the 
temporary cleans up the new buffer.  If it doesn't, *then* move the old 
data over (move must be nothrow).

-Howard

___
Unsubscribe  other changes: http://lists.boost.org/mailman/listinfo.cgi/boost


Re: [boost] Re: Review Request: cyclic_buffer

2003-06-10 Thread Howard Hinnant
On Tuesday, June 10, 2003, at 11:33  AM, Nigel Stewart wrote:

Constant time push/pop_front, constant time push/pop_back.  When begin
and end collide, reallocation automatically happens vector-like.
	Another point to consider:

Constant time push/pop can not be combined with std::vector
automatic re-allocation due to the linear cost of copying
into the realloc'd buffer.  Given that circular buffers
are often used in the context of real-time applications,
it seems to be consistant that push/pop from each end
should be truely constant time, and as a consequence the
the capacity is never implicitly changed unless explicitly
requested via reserve() or resize().
Clearly there is a need for what you are describing.  And clearly there 
is a need for what I am describing.  And clearly there are a few more 
closely related needs.

My opinion is that it is much easier to adapt a reallocating circular 
buffer to a non-reallocating one than vice-versa.  It is non-trivial to 
get a reallocating circular buffer correct with respect to exception 
safety, iterator/pointer invalidation, possible optimizations for types 
with trivial assign and copy, etc.  I would rather get the detailed, 
complicated work encapsulated in the container, and then have 
relatively simple adaptors a-la std::stack and std::queue.

An adapted construct-time-fixed-capacity circular buffer is no more 
inefficient or dangerous than a native container version.  And the 
adaptor is simple to write.  And because the adaptor is simple to 
write, there can be a different adaptor for every different need.

insert doesn't make sense?  Hide it.  It will never even be 
instantiated.  Don't want to reallocate?  Override push_back to check 
for size()==capacity() and then perform your favorite action (overwrite 
front, throw an exception, burn the toast, whatever).  Can only afford 
a reallocation if not at interrupt time?  Not a problem.  etc.

-Howard

___
Unsubscribe  other changes: http://lists.boost.org/mailman/listinfo.cgi/boost


[boost] Re: Review Request: cyclic_buffer

2003-06-10 Thread Alisdair Meredith
Nigel Stewart wrote:

 To summarise:
 
 Possible resize policies:
 
 i.  Capacity is fixed at compile time.
 ii. Capacity is fixed at construction time.
 iii.Capacity can be manually managed by client code.
 iv. Capacity is allowed to grow automatically. (ala std::vector)
 
 Possible insert handling policies:
 
 I.  Insert into full buffer results in no change.
 II. Insert into full buffer results in overwriting opposite end.
 Insertion into arbitrary position overwrites beginning, if necessary.
 III.Insert into full buffer results in exception.
 
 Possible resize to smaller capacity policies:
 
 1.  Keep left-most items. (ala std::vector)
 2.  Keep right-most items.
 
 The general gist of some random googling on circular buffers:
 
 - Automatic resizing does not appear to be common.
 - No established convention in relation to arbitrary insert.
 - No established convention in relation to resizing to smaller capacity.

Another distinction might be 'black box / white box', i.e. whether you
have public access to the iterators.  If you are merely using a fixed,
circular buffer to stop your queue/stack reallocating, you may want to
hide them.  

This is a relatively minor detail.  Something I would address in a
policy-based implementation, but not in the 'best generalisation for
common cases'.

-- 
AlisdairM

___
Unsubscribe  other changes: http://lists.boost.org/mailman/listinfo.cgi/boost


[boost] Re: Review Request: cyclic_buffer

2003-06-10 Thread Nigel Stewart
	Hi Howard,

Constant time push/pop can not be combined with std::vector
automatic re-allocation due to the linear cost of copying
into the realloc'd buffer.  
Clearly there is a need for what you are describing.  And clearly there 
is a need for what I am describing.  And clearly there are a few more 
closely related needs.
The generally accepted concept of a circular buffer
is a fixed size contiguous memory buffer.  Following
the principle of least surprise a circular_buffer
should not decide to resize itself.  To overwrite
items at the opposite end seems to bother some,
(it is a feature, or a bug?) it certainly seems to be
the generlly accepted concept of what a circular
buffer does.  (Unless the insertion fails, or the thread
is blocked until space becomes available)
Another reason against reallocation is the
possibility that two threads could utilise a
circular_buffer from each end in a
producer/consumer model, without the need
for explicit locking.  Having one thread decide
to move everything elsewhere in memory would
appear to exclude this style of use.
Another reason against reallocation is that
except for resize() and reserve() circular buffer
manipulation is _guaranteed_ to succeed, no
memory allocations occur which could possibly
fail.
My opinion is that it is much easier to adapt a reallocating circular 
buffer to a non-reallocating one than vice-versa.  
I don't agree, apart from the reasoning above.
What is the problem with intercepting push and insert
and reserving larger amounts of memory as necessary?
 I would rather get the detailed,
complicated work encapsulated in the container, and then have relatively 
simple adaptors a-la std::stack and std::queue.
We should aim to capture the true concept of a
circular buffer, perhaps.
(It will be interesting to see where all this leads
 to, in the end...)
	Cheers,

	Nigel Stewart



___
Unsubscribe  other changes: http://lists.boost.org/mailman/listinfo.cgi/boost


[boost] Re: Review Request: cyclic_buffer

2003-06-09 Thread Alisdair Meredith
Bo Persson wrote:

 Instead of dropping elements when the buffer is full, we might also
 consider waiting or throwing a failure.

The one true circular buffer template is a nigh impossible goal,
because it means so many things to different people.

A policy based approach would probably yield all the desirable
combinations of behaviour, but policy-based containers seem a hard sell
due to complexity of learning the interface.

The question of what to do when pushing new values into a full container
seems to be the key question though, and most likely to yield to the
policy approach.

One question that needs answering is what problem is the container
solving?

Last time I needed a circular buffer, I wanted fixed size (at compile
time) storing data samples (PODs or floats)  Using a fixed buffer solved
any memory fragmentation issues with extended running.  In my case I
simply wanted to overwrite the oldest data sample as new samples
arrived, as I was simply storing 4 minutes of data for viewing.  Values
wound never be popped, merely overwritten.

In this case, boost::arrayN would make a good base container.

Many other use-cases suggested over the evolution of this proposal have
made good cases for std::vector and std::deque.  Sometimes the buffer is
always full (eg my example above) in other cases comitted size varies as
items are push/popped.  Max size may be configured at compile-time, or
run-time.

I would certainly like the documentation to explain the tradeoffs made
in the implementation, and why this particular variation was chosen as
most appropriate for the general case.

-- 
AlisdairM

___
Unsubscribe  other changes: http://lists.boost.org/mailman/listinfo.cgi/boost


[boost] Re: Review Request: cyclic_buffer

2003-06-09 Thread Bo Persson

Alisdair Meredith [EMAIL PROTECTED] skrev i
meddelandet news:[EMAIL PROTECTED]
 Bo Persson wrote:

  Instead of dropping elements when the buffer is full, we might
also
  consider waiting or throwing a failure.

 The one true circular buffer template is a nigh impossible goal,
 because it means so many things to different people.


Yes, sure. I just reacted to the suggested names cyclic_buffer and
circular_buffer, which means slightly different things to me.

In one case we have a cycle, like in your case with a few minutes of
samples. The latest n samples are interesting, the older ones can be
discarded.

Another use for a circular (not cyclic :-) buffer is for the
producer-consumer case, where some objects are queued temporarily.
Here we can have the buffer wrap around at the ends, so we don't have
to move any objects after a pop_front(). We can redefine the front
position instead. If you run in a circle, you don't have an end case.





Bo Persson
[EMAIL PROTECTED]


___
Unsubscribe  other changes: http://lists.boost.org/mailman/listinfo.cgi/boost


[boost] Re: Review Request: cyclic_buffer

2003-06-09 Thread Nigel Stewart
Ok, ok, but what with insert? What element should be removed: the first or the last
one? And why the first approach or the latter.
Indeed, that's an issue.  Let's look at the possibilities:
(I'm speaking here about left/right or begin/end, rather than
 order in which items were inserted in FIFO fashion)
1.  Shift everything after pos along to the right, overwriting
the frontmost items if necessary.
2.  Shift everything before pos along to the left, overwriting
the backmost items if necessary.
3.  Disallow insertions that don't fit in the available
capacity, only push_front() and push_back() can utilise
the cyclic characteristic of the container.
	4.	Disallow insert completely.

	Pros and cons:

1.  + Most consistant with std::vector semantics.
+ Consistency with resize() semantics (rightmost items lost
  when shrinking)
2.  - Counter intuitive(?)
3.  - Forces client code to be capacity aware and do extra
  handling.
4.  - Consistency with other containers that support insert()
I would lean towards (1), since if (2) is desired then the client
code could swap which ends they push and pop from.  (3) and (4)
don't seem satisfactory from a completeness point of view.
   This would allow use as a LIFO (last-in, last-out)
   with fixed sized event horizon. (As an example.)
The best example I can suggest an undo buffer, such as in a
text editor, which should have a stack-like behaviour but
need not store an indefinite history.
I agree that a circular_buffer can't be all things to all
applications, but a generic general purpose container with
a concise and rational behaviour should be possible.  We
should look at ways that other policy could possibly be
layered on top of a generic circular_buffer.  (One experiment
would be whether std::stack and std::queue would behave
sensibly as circular_buffer adaptors.)  Let's explore these
before deciding that more containers or more elaborate
policy aspects are necessary to solve the vast majority
of uses.
	Cheers,

	Nigel

___
Unsubscribe  other changes: http://lists.boost.org/mailman/listinfo.cgi/boost


[boost] Re: Review Request: cyclic_buffer

2003-06-09 Thread Howard Hinnant
In article [EMAIL PROTECTED], Alisdair Meredith
[EMAIL PROTECTED] wrote:

| The one true circular buffer template is a nigh impossible goal,
| because it means so many things to different people.
snip
| I would certainly like the documentation to explain the tradeoffs made
| in the implementation, and why this particular variation was chosen as
| most appropriate for the general case.

Indeed!  Excellent post!

Perhaps container adaptors could be applied to a sufficiently
generalized circular buffer.

What is the one true queue?  There isn't one.  It is best a container
adaptor (policy based if you prefer).  I suspect that a more general
circular buffer, restricted by various adaptors, might serve more uses
with less code.

The circular buffer I've needed (and coded) fits none of aforementioned
descriptions.  But it is a circular buffer nonetheless.

For my money, the general circular buffer is one that exists in a
contiguous array, but with an arbitrary begin and end within that
array, capable of wrapping around the end of the physical memory:

+---+---+---+---+---+---+---+---+---+---+
| 4 | 5 |   |   |   |   | 0 | 1 | 2 | 3 |
+---+---+---+---+---+---+---+---+---+---+
  ^   ^
  |   |
 endbegin

Constant time push/pop_front, constant time push/pop_back.  When begin
and end collide, reallocation automatically happens vector-like.

This data structure can be efficiently coded with a 4-word data
structure.  For example:  data_ptr, capacity, size, begin_ptr  (there
are other variations that also only take 4 words).

From this you can write adaptors to constrain capacity, have push_back
overwrite front(), throw an exception on size() exceeding capacity(),
or whatever else you need to do.

You can't adapt any other std::container to do this job because vector
is the only one with capacity, but vector doesn't have a constant time
push/pop_front.  But I believe you can adapt the above described
container to meet the needs of other circular buffer requirements.  
For example:

template class T, class Container = general_cyclic_bufferT 
class my_cyclic_buffer
{
public:
   // typedefs ...

   my_cyclic_buffer(size_type cap) {c.reserve(cap);}

   reference operator[](size_type n) {return c[n];}
   // etc.

   void push_back(const value_type x)
   {
  if (capacity() != 0)
  {
 if (c.size() == c.capacity())
c.pop_front();
 c.push_back(x);
  }
   }

   change_capacity(size_type new_cap)
   {
  if (new_cap  c.capacity())
 c.reserve(new_cap);
  else if (new_cap  c.capacity())
  {
 c.erase(c.begin(), c.begin() + c.size() - new_cap);
 Container tmp(c);  // assumes tmp.capacity() == c.size()
 c.swap(tmp);
  }
   }
   // etc.
   
private:
   Container c;
};

-- 
Howard Hinnant
Metrowerks

___
Unsubscribe  other changes: http://lists.boost.org/mailman/listinfo.cgi/boost


[boost] Re: Review Request: cyclic_buffer

2003-06-06 Thread Nigel Stewart
Jan,

I'm looking forward to the opportunity of having a close look
at your cyclic_buffer.  But for now, a few perhaps shallow
comments...
1.  How about push_front()?  (in std::list style)
And pop_front()?
For the sake of generality.
The vague concept I have in mind is some kind
of sliding window algorithm that could be moving
either left or right, using a circular buffer as
a cache.  (Currently pondering a 2D version of a
circular buffer, anyone come across such a beast?)
2.  How about reserve(..) and resize(..)?
Shouldn't change_capacity be called reserve, like vector?
Shouldn't max_size be called capacity, like vector?
One context I which I've made happy use of a circular
buffer is for storing history in the form of a 3D
trail for moving particles.  It's handy to allow the
GUI to adjust the size of trails dynamically.
Semantics of resize should probably follow vector:
1. The capacity is implicitly adjusted to fit the
   requested size.  (Optional to explicitly manage
   capacity in addition to size)
2. Items are lost from the end if the container is
   to be shrunk.
(Oops, just noticed that resize is in one part of
 the documentation, but not the non-doxygen page).
For your reference, my somewhat more modest circular buffer:
http://www.nigels.com/glt/doc/classcbuffer.html
I am new to boost, but liking it very, very much...

Cheers,

Nigel Stewart

The cyclic buffer implementation and documentation (cyclic_buffer.zip) can be
found at:
http://groups.yahoo.com/group/boost/files/


___
Unsubscribe  other changes: http://lists.boost.org/mailman/listinfo.cgi/boost


Re: [boost] Re: Review Request: cyclic_buffer

2003-06-06 Thread jga
Hi Nigel!

Quoting Nigel Stewart [EMAIL PROTECTED]:

 Jan,
 
 I'm looking forward to the opportunity of having a close look
 at your cyclic_buffer.  But for now, a few perhaps shallow
 comments...
 
 1.How about push_front()?  (in std::list style)
   And pop_front()?
   For the sake of generality.
 
Imagine you have a cyclic buffer which is full. When you add (push_back or
insert) a new item into this buffer the first (oldest) item will be removed. I
want to emphasize the oldest. So if you want to push_front an item you want to
add the oldest one. But as soon as you add it the item will be removed because
it is the oltest item in the buffer. The buffer's content won't change. (The
same is valid for inserting at the beginning of the cyclic buffer.) This is the
reason why I didn't encompassed push_front in the implementation.

If there is no push_front then it is not logical to have pop_front. You can use
erase(begin) instead.

   The vague concept I have in mind is some kind
   of sliding window algorithm that could be moving
   either left or right, using a circular buffer as
   a cache.  (Currently pondering a 2D version of a
   circular buffer, anyone come across such a beast?)
 
 2.How about reserve(..) and resize(..)?
   Shouldn't change_capacity be called reserve, like vector?
If you look more closely at the documentation you will find out that
change_capacity is different from reserve. The capacity can be also decreased;
by reserve not.

   Shouldn't max_size be called capacity, like vector?
 
max_size in the vector is the largest possible size, not the reserved capacity.

   One context I which I've made happy use of a circular
   buffer is for storing history in the form of a 3D
   trail for moving particles.  It's handy to allow the
   GUI to adjust the size of trails dynamically.
 
   Semantics of resize should probably follow vector:
   1. The capacity is implicitly adjusted to fit the
  requested size.  (Optional to explicitly manage
  capacity in addition to size)
   2. Items are lost from the end if the container is
 to be shrunk.
Are you sure you want to lost the items at the end ? Not at the beginning (the
oldest) ?
BTW if you change_capacity in order to shrunk the container the oldest items
will be removed.

 
   (Oops, just noticed that resize is in one part of
the documentation, but not the non-doxygen page).
Thank you for this!

 
 For your reference, my somewhat more modest circular buffer:
 http://www.nigels.com/glt/doc/classcbuffer.html
 
 I am new to boost, but liking it very, very much...
 
 Cheers,
 
 Nigel Stewart
 
  The cyclic buffer implementation and documentation (cyclic_buffer.zip) can
 be
  found at:
  http://groups.yahoo.com/group/boost/files/
 
 
 ___
 Unsubscribe  other changes:
 http://lists.boost.org/mailman/listinfo.cgi/boost
 
 


___
Unsubscribe  other changes: http://lists.boost.org/mailman/listinfo.cgi/boost


[boost] Re: Review Request: cyclic_buffer

2003-06-06 Thread Nigel Stewart
Hi Jan,

1.  How about push_front()?  (in std::list style)
And pop_front()?
Imagine you have a cyclic buffer which is full. When you add (push_back or
insert) a new item into this buffer the first (oldest) item will be removed.
Ahh, I see that your conceptualisation is based on
age, or order of insertion.  Mine was more circular-
list oriented:  Adding to the front would overwite the
backmost item if the buffer is already full. Being a
circle, the buffer slides either left or right, as
needed.
In the case that the buffer is full and items are
alternatively added to the front and back, nothing
much happens.  Admittedly, a special case.
Perhaps our concepts are incompatible.  Looking at
Knuth's coverage of Circular Lists, insertion
is allowed at both ends.  Knuth also mentions
FIFO (first-in first-out) lists as being
sometimes called circular stores.  There
seems to be no particular implication of circular
meaning one way or the other, based on Knuth.
So the issue here seems to be whether a cyclic_buffer
should be circular-list-like or FIFO-like.  Given
that there is already a queue container adapter,
perhaps it's worth listing some applications
of cyclic_buffer that depend on either
circular-list-like or FIFO-like semantics.
Could a queue adaptor using a cyclic_buffer instead
of a deque provide the explicitly FIFO-like mode of
operation?  (While leaving cyclic_buffer to be
circular-list-like?)
	Semantics of resize should probably follow vector:
	1. The capacity is implicitly adjusted to fit the
	   requested size.  
	http://www.sgi.com/tech/stl/Vector.html

	vector::resize

Inserts or erases elements at the end such that
the size becomes n.
(I think a developer may find it surprising for a
 cyclic_buffer::resize to be clamped to the capacity.)
2. Items are lost from the end if the container is
   to be shrunk.
Are you sure you want to lost the items at the end ? Not at the beginning (the

In the FIFO-view, the leftmost/oldest should be removed, agreed.
In the circular-list view, it is not so obvious - so follow
vector and erase the rightmost... :-)
	Have a nice weekend, I'm off to the country side...

	Nigel



___
Unsubscribe  other changes: http://lists.boost.org/mailman/listinfo.cgi/boost