C code is used to implement some common data structures, such as list, map, tree, string function, and ring buffer. It is worth looking at people who learn C language.
The boost Library also has the implementation of a circular buffer. The specific example is as follows:
[Cpp]
# Include <boost/circular_buffer.hpp>
Int main (int/* argc */, char */* argv */[]) {
// Create a circular buffer to store data of three int types
Boost: circular_buffer <int> cb (3 );
// Insert element
Cb. push_back (1 );
Cb. push_back (2 );
Cb. push_back (3 );
Int a = cb [0]; // a = 1
Int B = cb [1]; // B = 2
Int c = cb [2]; // c = 3
// The Ring buffer is full now. If you continue to insert an element, the first element will be overwritten.
Cb. push_back (4); // cover 1 with 4
Cb. push_back (5); // overwrite 2 with 5
// The Ring buffer currently contains elements 3, 4, and 5.
A = cb [0]; // a = 3
B = cb [1]; // B = 4
C = cb [2]; // c = 5
// Elements can be extracted from the back or from the front
Cb. pop_back (); // 5 retrieved
Cb. pop_front (); // 3 retrieved
Int d = cb [0]; // d = 4
Return 0;
}
# Include <boost/circular_buffer.hpp>
Int main (int/* argc */, char */* argv */[]) {
// Create a circular buffer to store data of three int types
Boost: circular_buffer <int> cb (3 );
// Insert element
Cb. push_back (1 );
Cb. push_back (2 );
Cb. push_back (3 );
Int a = cb [0]; // a = 1
Int B = cb [1]; // B = 2
Int c = cb [2]; // c = 3
// The Ring buffer is full now. If you continue to insert an element, the first element will be overwritten.
Cb. push_back (4); // cover 1 with 4
Cb. push_back (5); // overwrite 2 with 5
// The Ring buffer currently contains elements 3, 4, and 5.
A = cb [0]; // a = 3
B = cb [1]; // B = 4
C = cb [2]; // c = 5
// Elements can be extracted from the back or from the front
Cb. pop_back (); // 5 retrieved
Cb. pop_front (); // 3 retrieved
Int d = cb [0]; // d = 4
Return 0;
}
The above example is very simple. It gives us a simple understanding of the usage method.
If you are interested, you can also take a look at the example below.
[Cpp]
# Include <boost/circular_buffer.hpp>
# Include <numeric>
# Include <assert. h>
Int main (int/* argc */, char */* argv */[])
{
// Create an annular buffer with a capacity of 3
Boost: circular_buffer <int> cb (3 );
// Insert two elements into the circular buffer.
Cb. push_back (1 );
Cb. push_back (2 );
// Assertions
Assert (cb [0] = 1 );
Assert (cb [1] = 2 );
Assert (! Cb. full ());
Assert (cb. size () = 2 );
Assert (cb. capacity () = 3 );
// Insert two more elements
Cb. push_back (3 );
Cb. push_back (4 );
// Calculate the sum of all elements in the container
Int sum = std: accumulate (cb. begin (), cb. end (), 0 );
// Assertions
Assert (cb [0] = 2 );
Assert (cb [1] = 3 );
Assert (cb [2] = 4 );
Assert (* cb. begin () = 2 );
Assert (cb. front () = 2 );
Assert (cb. back () = 4 );
Assert (sum = 9 );
Assert (cb. full ());
Assert (cb. size () = 3 );
Assert (cb. capacity () = 3 );
Return 0;
}
# Include <boost/circular_buffer.hpp>
# Include <numeric>
# Include <assert. h>
Int main (int/* argc */, char */* argv */[])
{
// Create an annular buffer with a capacity of 3
Boost: circular_buffer <int> cb (3 );
// Insert two elements into the circular buffer.
Cb. push_back (1 );
Cb. push_back (2 );
// Assertions
Assert (cb [0] = 1 );
Assert (cb [1] = 2 );
Assert (! Cb. full ());
Assert (cb. size () = 2 );
Assert (cb. capacity () = 3 );
// Insert two more elements
Cb. push_back (3 );
Cb. push_back (4 );
// Calculate the sum of all elements in the container
Int sum = std: accumulate (cb. begin (), cb. end (), 0 );
// Assertions
Assert (cb [0] = 2 );
Assert (cb [1] = 3 );
Assert (cb [2] = 4 );
Assert (* cb. begin () = 2 );
Assert (cb. front () = 2 );
Assert (cb. back () = 4 );
Assert (sum = 9 );
Assert (cb. full ());
Assert (cb. size () = 3 );
Assert (cb. capacity () = 3 );
Return 0;
}
There is also a special ring buffer called the boundary buffer. The boundary buffer is a typical producer consumer mode. The producer produces and stores elements. The consumer extracts elements and then processes them. The boundary buffer has a special place, that is, when the buffer is full, the producer ensures that the element is not inserted and inserts new elements until there is free space.
The implementation of the border buffer is as follows:
[Cpp]
# Include <boost/circular_buffer.hpp>
# Include <boost/thread/mutex. hpp>
# Include <boost/thread/condition. hpp>
# Include <boost/thread. hpp>
# Include <boost/call_traits.hpp>
# Include <boost/progress. hpp>
# Include <boost/bind. hpp>
Template <class T>
Class bounded_buffer {
Public:
Typedef boost: circular_buffer <T> container_type;
Typedef typename container_type: size_type;
Typedef typename container_type: value_type;
Typedef typename boost: call_traits <value_type >:: param_type;
Explicit bounded_buffer (size_type capacity): m_unread (0), m_container (capacity ){}
Void push_front (boost: call_traits <value_type >:: param_type item ){
// Param_type represents the "best" way to pass a parameter of type value_type to a method
Boost: mutex: scoped_lock lock (m_mutex );
M_not_full.wait (lock, boost: bind (& bounded_buffer <value_type >:: is_not_full, this ));
M_container.push_front (item );
++ M_unread;
Lock. unlock ();
M_not_empty.policy_one ();
}
Void pop_back (value_type * pItem ){
Boost: mutex: scoped_lock lock (m_mutex );
M_not_empty.wait (lock, boost: bind (& bounded_buffer <value_type >:: is_not_empty, this ));
* PItem = m_container [-- m_unread];
Lock. unlock ();
M_not_full.policy_one ();
}
Private:
Bounded_buffer (const bounded_buffer &); // Disabled copy constructor
Bounded_buffer & operator = (const bounded_buffer &); // Disabled assign operator
Bool is_not_empty () const {return m_unread> 0 ;}
Bool is_not_full () const {return m_unread <m_container.capacity ();}
Size_type m_unread;
Container_type m_container;
Boost: mutex m_mutex;
Boost: condition m_not_empty;
Boost: condition m_not_full;
};
# Include <boost/circular_buffer.hpp>
# Include <boost/thread/mutex. hpp>
# Include <boost/thread/condition. hpp>
# Include <boost/thread. hpp>
# Include <boost/call_traits.hpp>
# Include <boost/progress. hpp>
# Include <boost/bind. hpp>
Template <class T>
Class bounded_buffer {
Public:
Typedef boost: circular_buffer <T> container_type;
Typedef typename container_type: size_type;
Typedef typename container_type: value_type;
Typedef typename boost: call_traits <value_type >:: param_type;
Explicit bounded_buffer (size_type capacity): m_unread (0), m_container (capacity ){}
Void push_front (boost: call_traits <value_type >:: param_type item ){
// Param_type represents the "best" way to pass a parameter of type value_type to a method
Boost: mutex: scoped_lock lock (m_mutex );
M_not_full.wait (lock, boost: bind (& bounded_buffer <value_type >:: is_not_full, this ));
M_container.push_front (item );
++ M_unread;
Lock. unlock ();
M_not_empty.policy_one ();
}
Void pop_back (value_type * pItem ){
Boost: mutex: scoped_lock lock (m_mutex );
M_not_empty.wait (lock, boost: bind (& bounded_buffer <value_type >:: is_not_empty, this ));
* PItem = m_container [-- m_unread];
Lock. unlock ();
M_not_full.policy_one ();
}
Private:
Bounded_buffer (const bounded_buffer &); // Disabled copy constructor
Bounded_buffer & operator = (const bounded_buffer &); // Disabled assign operator
Bool is_not_empty () const {return m_unread> 0 ;}
Bool is_not_full () const {return m_unread <m_container.capacity ();}
Size_type m_unread;
Container_type m_container;
Boost: mutex m_mutex;
Boost: condition m_not_empty;
Boost: condition m_not_full;
};
1. The push_front () method is called by the producer thread to insert new elements into the buffer. This method locks the mutex and waits until a new space is available to insert new elements. (The Mutex lock is not locked during the waiting period and will be locked only when the conditions are met.) If there is an available space in the buffer, the execution will continue, this method inserts an element into the end of the circular buffer. Then, the number of unread elements increases and is automatically unlocked. (In this example, the Mutex lock will throw an exception and the lock will be automatically opened in the destructor of the scoped_lock object ). finally, this method will notify one of the consumer threads that a new element is inserted into the buffer zone.
2. The pop_back () method is called by the consumer thread to read the next element from the buffer. This method locks the Mutex and waits until an unread element enters the buffer zone. If there is at least one unread element, this method will reduce the number of unread elements and then read an unread element from circular_buffer. Then unlock the Mutex and notify a producer thread in the waiting state to tell it that new space can be inserted with new elements.
3. This pop_back () method removes elements and the elements remain in circular_buffer. In this way, when the circular_buffer is full, it will be replaced by a new element by the producer thread. This technology is more efficient than removing an element.
The following url is a C ++ Implementation of the ring buffer, which can be used to process binary data. If you have time to translate it, you can read it easily.
Circular Buffer of Raw Binary Data in C ++
Circular Buffer, Cyclic Buffer or Ring Buffer is a data structure that extends tively manages a queue of some items. items can be added at the back and removed from the front. it has limited capacity because it is based on preallocated array. functionality is implemented using two pointers or indices-pointing to the first and past the last valid element. the Begin pointer is incremented whenever an item is popped from the front so that it "chases" the End pointer, which is incremented whenever a new item is pushed to the back. they can both wrap around the size of the array. both operations are done very between tively-in constant time O (1) and no reallocations are needed. this makes circular buffers perfect solution for queues of some data streams, like video or audio.
It's not very sophisticated data structure, but there is one problem. sample codes of circular buffers you can find on the Internet, just like for your other data structures, operate usually on a single object of some user-defined type. what if we need a buffer for raw binary data, stored as array of bytes? We can treat single bytes as data items, but enqueueing and dequeueing single bytes with separate function CILS wocould not be efficient. we can, on the other hand, define some block of data (like 4096 bytes) as the type of item, but this limits us to operating on such block at a time.
Best solution wocould be to write an implementation that operates on binary data in form of (const char * bytes, size_t byte_count) and allows writing and reading arbitrary amount of data in a single call, just like functions for writing and reading files do. the only problem that arises in such code is that sometimes the block of data you want to write to or read from the buffer is not in a continuous region of memory, but wraps around to the beginning of the array so we have to process it on two parts-first at the end of the array and the second at the beginning.
Here is my C ++ implementation of a circular buffer for raw binary data:
# Include <algorithm> // for std: min
Class CircularBuffer
{
Public:
CircularBuffer (size_t capacity );
~ CircularBuffer ();
Size_t size () const {return size _;}
Size_t capacity () const {return capacity _;}
// Return number of bytes written.
Size_t write (const char * data, size_t bytes );
// Return number of bytes read.
Size_t read (char * data, size_t bytes );
Private:
Size_t beg_index _, end_index _, size _, capacity _;
Char * data _;
};
CircularBuffer: CircularBuffer (size_t capacity)
: Beg_index _ (0)
, End_index _ (0)
, Size _ (0)
, Capacity _ (capacity)
{
Data _ = new char [capacity];
}
CircularBuffer ::~ CircularBuffer ()
{
Delete [] data _;
}
Size_t CircularBuffer: write (const char * data, size_t bytes)
{
If (bytes = 0) return 0;
Size_t capacity = capacity _;
Size_t bytes_to_write = std: min (bytes, capacity-size _);
// Write in a single step
If (bytes_to_write <= capacity-end_index _)
{
Memcpy (data _ + end_index _, data, bytes_to_write );
End_index _ + = bytes_to_write;
If (end_index _ = capacity) end_index _ = 0;
}
// Write in two steps
Else
{
Size_t size_1 = capacity-end_index _;
Memcpy (data _ + end_index _, data, size_1 );
Size_t size_2 = bytes_to_write-size_1;
Memcpy (data _, data + size_1, size_2 );
End_index _ = size_2;
}
Size _ + = bytes_to_write;
Return bytes_to_write;
}
Size_t CircularBuffer: read (char * data, size_t bytes)
{
If (bytes = 0) return 0;
Size_t capacity = capacity _;
Size_t bytes_to_read = std: min (bytes, size _);
// Read in a single step
If (bytes_to_read <= capacity-beg_index _)
{
Memcpy (data, data _ + beg_index _, bytes_to_read );
Beg_index _ + = bytes_to_read;
If (beg_index _ = capacity) beg_index _ = 0;
}
// Read in two steps
Else
{
Size_t size_1 = capacity-beg_index _;
Memcpy (data, data _ + beg_index _, size_1 );
Size_t size_2 = bytes_to_read-size_1;
Memcpy (data + size_1, data _, size_2 );
Beg_index _ = size_2;
}
Size _-= bytes_to_read;
Return bytes_to_read;
} Similar phenomenon can be observed in API of the FMOD sound library. just like graphical textures in DirectX, sound samples in FMOD can also be "locked" to get pointer to a raw memory we can read or fill. but DirectX textures lie in the continuous memory region, so we get a single pointer. the only difficult thing in understanding locking textures is the concept of "stride", which can be greater than the width of a single row. here in FMOD the Sound: lock () method returns two pointers and two lengths, probably because the locked region can wrap over end of internally used circular buffer like the one shown above.