...one of the most highly
regarded and expertly designed C++ library projects in the
world.
— Herb Sutter and Andrei
Alexandrescu, C++
Coding Standards
This example shows several functions, including summing all valid values.
#include <boost/circular_buffer.hpp> #include <numeric> #include <assert.h> int main(int /*argc*/, char* /*argv*/[]) { // Create a circular buffer of capacity 3. boost::circular_buffer<int> cb(3); assert(cb.capacity() == 3); // Check is empty. assert(cb.size() == 0); assert(cb.empty()); // Insert some elements into the circular buffer. cb.push_back(1); cb.push_back(2); // Assertions to check push_backs have expected effect. assert(cb[0] == 1); assert(cb[1] == 2); assert(!cb.full()); assert(cb.size() == 2); assert(cb.capacity() == 3); // Insert some other elements. cb.push_back(3); cb.push_back(4); // Evaluate the sum of all elements. int sum = std::accumulate(cb.begin(), cb.end(), 0); // Assertions to check state. assert(sum == 9); 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(cb.full()); assert(cb.size() == 3); assert(cb.capacity() == 3); return 0; }
The circular_buffer
has
a capacity of three int
. Therefore,
the size of the buffer will never exceed three. The std::accumulate
algorithm evaluates the sum of the stored elements. The semantics of the circular_buffer
can be inferred from
the assertions.
You can see the full example code at circular_buffer_sum_example.cpp.
The bounded buffer is normally used in a producer-consumer mode: producer threads produce items and store them in the container and consumer threads remove these items and process them. The bounded buffer has to guarantee that
This example shows how the circular_buffer
can be utilized as an underlying container of the bounded buffer.
#include <boost/circular_buffer.hpp> #include <boost/thread/mutex.hpp> #include <boost/thread/condition.hpp> #include <boost/thread/thread.hpp> #include <boost/call_traits.hpp> #include <boost/bind.hpp> #include <boost/timer/timer.hpp> // for auto_cpu_timer template <class T> class bounded_buffer { public: typedef boost::circular_buffer<T> container_type; typedef typename container_type::size_type size_type; typedef typename container_type::value_type value_type; typedef typename boost::call_traits<value_type>::param_type param_type; explicit bounded_buffer(size_type capacity) : m_unread(0), m_container(capacity) {} void push_front(typename 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.notify_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.notify_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; }; //
The bounded_buffer relies on Boost.Thread and Boost.Bind libraries and Boost.call_traits utility.
The push_front()
method is called by the producer thread in order to insert a new item into
the buffer. The method locks the mutex and waits until there is a space for
the new item. (The mutex is unlocked during the waiting stage and has to be
regained when the condition is met.) If there is a space in the buffer available,
the execution continues and the method inserts the item at the end of the
circular_buffer
. Then it
increments the number of unread items and unlocks the mutex (in case an exception
is thrown before the mutex is unlocked, the mutex is unlocked automatically
by the destructor of the scoped_lock). At last the method notifies one of the
consumer threads waiting for a new item to be inserted into the buffer.
The pop_back()
method is called by the consumer thread in order to read the next item from
the buffer. The method locks the mutex and waits until there is an unread item
in the buffer. If there is at least one unread item, the method decrements
the number of unread items and reads the next item from the circular_buffer
.
Then it unlocks the mutex and notifies one of the producer threads waiting
for the buffer to free a space for the next item.
The bounded buffer::pop_back()
method does not remove
the item but the item is left in the circular_buffer which then
replaces it with a new one (inserted by a
producer) when the circular_buffer is full. This technique is more effective
than removing the item explicitly by calling the circular_buffer::pop_back()
method of the circular_buffer
.
This claim is based on the assumption that an assignment (replacement) of a new item into an old one is more effective than a destruction (removal) of an old item and a consequent inplace construction (insertion) of a new item.
For comparison of bounded buffers based on different containers compile and
run bounded_buffer_comparison.cpp.
The test should reveal the bounded buffer based on the circular_buffer
is most effective closely followed by the std::deque
based
bounded buffer. (In reality, the result may differ sometimes because the test
is always affected by external factors such as immediate CPU load.)
You can see the full test code at bounded_buffer_comparison.cpp, and an example of output is
Description: Autorun "J:\Cpp\Misc\Debug\bounded_buffer_comparison.exe" bounded_buffer<int> 5.15 s bounded_buffer_space_optimized<int> 5.71 s bounded_buffer_deque_based<int> 15.57 s bounded_buffer_list_based<int> 17.33 s bounded_buffer<std::string> 24.49 s bounded_buffer_space_optimized<std::string> 28.33 s bounded_buffer_deque_based<std::string> 29.45 s bounded_buffer_list_based<std::string> 31.29 s
.