Boost logo

Boost :

From: Howard Hinnant (hinnant_at_[hidden])
Date: 2003-06-09 20:09:54


In article <3EE4BB8A.1D8AB0E2_at_[hidden]>, Alisdair Meredith
<alisdair.meredith_at_[hidden]> 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 |
+---+---+---+---+---+---+---+---+---+---+
          ^ ^
          | |
         end begin

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_buffer<T> >
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

Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk