Circular / Cyclic Buffer implementation












3














I am learning circular / cyclic buffer using this link. Here is the code that I have written so far:



#include <iostream>
#include <memory>
#include <cstddef>

class CircularBuffer
{
std::unique_ptr<int> u_ptr;
std::size_t head;
std::size_t tail;
const std::size_t capacity;
bool full_v;
public:
CircularBuffer(std::size_t);
void reset();
bool full() const;
bool empty() const;
std::size_t size() const;
void write(int);
int read();
};

// Constructor
// Set the capacity via initializer list because it is const
CircularBuffer::CircularBuffer(std::size_t space):capacity{space}
{
u_ptr = std::unique_ptr<int>(new int[space]);
head = 0;
tail = 0;
full_v = false;
}

// Reset the Buffer
void CircularBuffer::reset()
{
head = 0;
tail = 0;
full_v = false;
u_ptr[head] = int{};
}

// Check if buffer is full
bool CircularBuffer::full() const
{
return full_v;
}

// Check if buffer is empty
bool CircularBuffer::empty() const
{
return (!full_v && head == tail);
}

// Return the size of the buffer
std::size_t CircularBuffer::size() const
{
if(full_v)
{
return capacity;
}
if(head >= tail)
{
return head - tail;
}
else
{
return capacity - (tail - head);
}
}

// Write values into the buffer
void CircularBuffer::write(int data)
{
u_ptr[head] = data;
head = (head + 1) % capacity;
if(full_v)
{
tail = (tail + 1) % capacity;
}
full_v = (head == tail);
}

// Read from buffer
int CircularBuffer::read()
{
if(this -> empty())
{
return int{};
}
int ret_val = u_ptr[tail];
full_v = false;
tail = (tail + 1) % capacity;
return ret_val;
}

int main()
{
CircularBuffer cb{10};
std::cout << "Empty: " << cb.empty() << "n";
for(int i = 0; i < 10; i++)
{
cb.write(i);
}
std::cout << "Full: " << cb.full() << "n";
std::cout << "Read: " << cb.read() << "n";
std::cout << "Full: " << cb.full() << "n";
std::cout << "Empty: " << cb.empty() << "n";
std::cout << "Size: " << cb.size() << "n";
cb.write(35);
std::cout << "Size: " << cb.size() << "n";
std::cout << "Read: " << cb.read() << "n";
std::cout << "Size: " << cb.size() << "n";
cb.reset();
std::cout << "Size: " << cb.size() << "n";
return 0;
}



  1. Is the implementation correct? What are the shortcomings that can be fixed?


  2. I see that the original reference uses a mutex object and uses lock. Is such an approach used with all data structures? If not, is there a reason why that has been used here?











share|improve this question





























    3














    I am learning circular / cyclic buffer using this link. Here is the code that I have written so far:



    #include <iostream>
    #include <memory>
    #include <cstddef>

    class CircularBuffer
    {
    std::unique_ptr<int> u_ptr;
    std::size_t head;
    std::size_t tail;
    const std::size_t capacity;
    bool full_v;
    public:
    CircularBuffer(std::size_t);
    void reset();
    bool full() const;
    bool empty() const;
    std::size_t size() const;
    void write(int);
    int read();
    };

    // Constructor
    // Set the capacity via initializer list because it is const
    CircularBuffer::CircularBuffer(std::size_t space):capacity{space}
    {
    u_ptr = std::unique_ptr<int>(new int[space]);
    head = 0;
    tail = 0;
    full_v = false;
    }

    // Reset the Buffer
    void CircularBuffer::reset()
    {
    head = 0;
    tail = 0;
    full_v = false;
    u_ptr[head] = int{};
    }

    // Check if buffer is full
    bool CircularBuffer::full() const
    {
    return full_v;
    }

    // Check if buffer is empty
    bool CircularBuffer::empty() const
    {
    return (!full_v && head == tail);
    }

    // Return the size of the buffer
    std::size_t CircularBuffer::size() const
    {
    if(full_v)
    {
    return capacity;
    }
    if(head >= tail)
    {
    return head - tail;
    }
    else
    {
    return capacity - (tail - head);
    }
    }

    // Write values into the buffer
    void CircularBuffer::write(int data)
    {
    u_ptr[head] = data;
    head = (head + 1) % capacity;
    if(full_v)
    {
    tail = (tail + 1) % capacity;
    }
    full_v = (head == tail);
    }

    // Read from buffer
    int CircularBuffer::read()
    {
    if(this -> empty())
    {
    return int{};
    }
    int ret_val = u_ptr[tail];
    full_v = false;
    tail = (tail + 1) % capacity;
    return ret_val;
    }

    int main()
    {
    CircularBuffer cb{10};
    std::cout << "Empty: " << cb.empty() << "n";
    for(int i = 0; i < 10; i++)
    {
    cb.write(i);
    }
    std::cout << "Full: " << cb.full() << "n";
    std::cout << "Read: " << cb.read() << "n";
    std::cout << "Full: " << cb.full() << "n";
    std::cout << "Empty: " << cb.empty() << "n";
    std::cout << "Size: " << cb.size() << "n";
    cb.write(35);
    std::cout << "Size: " << cb.size() << "n";
    std::cout << "Read: " << cb.read() << "n";
    std::cout << "Size: " << cb.size() << "n";
    cb.reset();
    std::cout << "Size: " << cb.size() << "n";
    return 0;
    }



    1. Is the implementation correct? What are the shortcomings that can be fixed?


    2. I see that the original reference uses a mutex object and uses lock. Is such an approach used with all data structures? If not, is there a reason why that has been used here?











    share|improve this question



























      3












      3








      3







      I am learning circular / cyclic buffer using this link. Here is the code that I have written so far:



      #include <iostream>
      #include <memory>
      #include <cstddef>

      class CircularBuffer
      {
      std::unique_ptr<int> u_ptr;
      std::size_t head;
      std::size_t tail;
      const std::size_t capacity;
      bool full_v;
      public:
      CircularBuffer(std::size_t);
      void reset();
      bool full() const;
      bool empty() const;
      std::size_t size() const;
      void write(int);
      int read();
      };

      // Constructor
      // Set the capacity via initializer list because it is const
      CircularBuffer::CircularBuffer(std::size_t space):capacity{space}
      {
      u_ptr = std::unique_ptr<int>(new int[space]);
      head = 0;
      tail = 0;
      full_v = false;
      }

      // Reset the Buffer
      void CircularBuffer::reset()
      {
      head = 0;
      tail = 0;
      full_v = false;
      u_ptr[head] = int{};
      }

      // Check if buffer is full
      bool CircularBuffer::full() const
      {
      return full_v;
      }

      // Check if buffer is empty
      bool CircularBuffer::empty() const
      {
      return (!full_v && head == tail);
      }

      // Return the size of the buffer
      std::size_t CircularBuffer::size() const
      {
      if(full_v)
      {
      return capacity;
      }
      if(head >= tail)
      {
      return head - tail;
      }
      else
      {
      return capacity - (tail - head);
      }
      }

      // Write values into the buffer
      void CircularBuffer::write(int data)
      {
      u_ptr[head] = data;
      head = (head + 1) % capacity;
      if(full_v)
      {
      tail = (tail + 1) % capacity;
      }
      full_v = (head == tail);
      }

      // Read from buffer
      int CircularBuffer::read()
      {
      if(this -> empty())
      {
      return int{};
      }
      int ret_val = u_ptr[tail];
      full_v = false;
      tail = (tail + 1) % capacity;
      return ret_val;
      }

      int main()
      {
      CircularBuffer cb{10};
      std::cout << "Empty: " << cb.empty() << "n";
      for(int i = 0; i < 10; i++)
      {
      cb.write(i);
      }
      std::cout << "Full: " << cb.full() << "n";
      std::cout << "Read: " << cb.read() << "n";
      std::cout << "Full: " << cb.full() << "n";
      std::cout << "Empty: " << cb.empty() << "n";
      std::cout << "Size: " << cb.size() << "n";
      cb.write(35);
      std::cout << "Size: " << cb.size() << "n";
      std::cout << "Read: " << cb.read() << "n";
      std::cout << "Size: " << cb.size() << "n";
      cb.reset();
      std::cout << "Size: " << cb.size() << "n";
      return 0;
      }



      1. Is the implementation correct? What are the shortcomings that can be fixed?


      2. I see that the original reference uses a mutex object and uses lock. Is such an approach used with all data structures? If not, is there a reason why that has been used here?











      share|improve this question















      I am learning circular / cyclic buffer using this link. Here is the code that I have written so far:



      #include <iostream>
      #include <memory>
      #include <cstddef>

      class CircularBuffer
      {
      std::unique_ptr<int> u_ptr;
      std::size_t head;
      std::size_t tail;
      const std::size_t capacity;
      bool full_v;
      public:
      CircularBuffer(std::size_t);
      void reset();
      bool full() const;
      bool empty() const;
      std::size_t size() const;
      void write(int);
      int read();
      };

      // Constructor
      // Set the capacity via initializer list because it is const
      CircularBuffer::CircularBuffer(std::size_t space):capacity{space}
      {
      u_ptr = std::unique_ptr<int>(new int[space]);
      head = 0;
      tail = 0;
      full_v = false;
      }

      // Reset the Buffer
      void CircularBuffer::reset()
      {
      head = 0;
      tail = 0;
      full_v = false;
      u_ptr[head] = int{};
      }

      // Check if buffer is full
      bool CircularBuffer::full() const
      {
      return full_v;
      }

      // Check if buffer is empty
      bool CircularBuffer::empty() const
      {
      return (!full_v && head == tail);
      }

      // Return the size of the buffer
      std::size_t CircularBuffer::size() const
      {
      if(full_v)
      {
      return capacity;
      }
      if(head >= tail)
      {
      return head - tail;
      }
      else
      {
      return capacity - (tail - head);
      }
      }

      // Write values into the buffer
      void CircularBuffer::write(int data)
      {
      u_ptr[head] = data;
      head = (head + 1) % capacity;
      if(full_v)
      {
      tail = (tail + 1) % capacity;
      }
      full_v = (head == tail);
      }

      // Read from buffer
      int CircularBuffer::read()
      {
      if(this -> empty())
      {
      return int{};
      }
      int ret_val = u_ptr[tail];
      full_v = false;
      tail = (tail + 1) % capacity;
      return ret_val;
      }

      int main()
      {
      CircularBuffer cb{10};
      std::cout << "Empty: " << cb.empty() << "n";
      for(int i = 0; i < 10; i++)
      {
      cb.write(i);
      }
      std::cout << "Full: " << cb.full() << "n";
      std::cout << "Read: " << cb.read() << "n";
      std::cout << "Full: " << cb.full() << "n";
      std::cout << "Empty: " << cb.empty() << "n";
      std::cout << "Size: " << cb.size() << "n";
      cb.write(35);
      std::cout << "Size: " << cb.size() << "n";
      std::cout << "Read: " << cb.read() << "n";
      std::cout << "Size: " << cb.size() << "n";
      cb.reset();
      std::cout << "Size: " << cb.size() << "n";
      return 0;
      }



      1. Is the implementation correct? What are the shortcomings that can be fixed?


      2. I see that the original reference uses a mutex object and uses lock. Is such an approach used with all data structures? If not, is there a reason why that has been used here?








      c++ pointers locking circular-list






      share|improve this question















      share|improve this question













      share|improve this question




      share|improve this question








      edited Sep 30 at 21:08









      Jamal

      30.2k11116226




      30.2k11116226










      asked Sep 30 at 18:27









      skr_robo

      309210




      309210






















          2 Answers
          2






          active

          oldest

          votes


















          3














          Your ringbuffer has a compile time size, so it would be appropriate to make it a template class rather than simply passing it as an argument to the constructor. You never plan to change it anyway?



          template<size_t bufSize>
          class CircularBuffer


          That leads to the next point. You use a std::unique_ptr<int> There is something in C++ that is much better than this std::array. With the size of the buffer a compile time template argument of the class you can easily use it



          template<size_t bufSize>
          class CircularBuffer {
          std::array<int, bufSize> buf;
          std::size_t head;
          std::size_t tail;
          bool full_v;

          ...
          };


          Note that this increases the size of the class object considerably, as the std::array is directly inlined in the class.



          The other members of this class are always the same after construction so you should use static member initialization:



          template<size_t bufSize>
          class CircularBuffer {
          std::array<int, bufSize> buf;
          std::size_t head{ 0 };
          std::size_t tail{ 0 };
          bool full_v{ false };

          ...
          };


          Note that this completely removes the need to define a constructor at all. The compiler generated default constructor will do just fine.



          Resetting the buffer should not change the data stored in it as it is not read anyway so just leave it alone



          void CircularBuffer::reset()
          {
          head = 0;
          tail = 0;
          full_v = false;
          }


          Can tail ever be larger than head? If not why are you checking it.






          share|improve this answer





















          • Thank You for the review. Imagine a situation where buffer is full and both head and tail are in the middle of the buffer. Then reading happens. The tail keeps increasing while head remains in its position. So, in this case, tail can be ahead of head.
            – skr_robo
            Sep 30 at 19:03



















          3














          Correctness and design



          I will skip all points miscco
          already raised and go directly to void write and int read.




          • Your write overwrites old values when the buffer is full.

          • Your read returns zero if the buffer is empty.


          Although understandable, it should at least be documented:



          /// Describe the class, what is the purpose, what is important
          template<class T, size_t N> // see std::array
          class CircularBuffer {
          ...
          /// Write values into the buffer, overwrite old values when the buffer is full
          void write(const T& data) {


          or maybe:



              /// Write values into the buffer
          ///return true if written, false if buffer was full
          bool write(const T& data) {


          You can also notice that I made it a template, just like std::array - both the type and the capacity are now template parameters. The standard way of passing arguments to methods like write is to use const T&, but it has some drawbacks, so, if it trully is for embedded systems, then simple T is an option as well (knowing you are going to use it with integers and such). But document it (and/or add other template parameters with some defaults).



          Synchronization (Locking)



          If you are going to use it with multiple threads (or main loop vs. interrupt) then some form of synchronization is indeed needed, because there are possible race-conditions otherwise, like one thread being inside write, just overwriting the oldest slot, while another thread can be inside read getting this newest element, while it should get the oldest, but tail was not yet updated by the first thread.



          First question to ask is: How many threads / concurrent readers and writers (producers and consumers)?
          Generic solution could use mutex but there can be better solutions for single producer single consumer (e.g. main loop vs. interrupt - USART and similar communication synchronization). One possibility is to use separate (atomic) write and read counters (like head and tail but you always only increase the counters and substract them to get number of elements inside the buffer - but be careful with order of operations, it is not trivial).






          share|improve this answer























            Your Answer





            StackExchange.ifUsing("editor", function () {
            return StackExchange.using("mathjaxEditing", function () {
            StackExchange.MarkdownEditor.creationCallbacks.add(function (editor, postfix) {
            StackExchange.mathjaxEditing.prepareWmdForMathJax(editor, postfix, [["\$", "\$"]]);
            });
            });
            }, "mathjax-editing");

            StackExchange.ifUsing("editor", function () {
            StackExchange.using("externalEditor", function () {
            StackExchange.using("snippets", function () {
            StackExchange.snippets.init();
            });
            });
            }, "code-snippets");

            StackExchange.ready(function() {
            var channelOptions = {
            tags: "".split(" "),
            id: "196"
            };
            initTagRenderer("".split(" "), "".split(" "), channelOptions);

            StackExchange.using("externalEditor", function() {
            // Have to fire editor after snippets, if snippets enabled
            if (StackExchange.settings.snippets.snippetsEnabled) {
            StackExchange.using("snippets", function() {
            createEditor();
            });
            }
            else {
            createEditor();
            }
            });

            function createEditor() {
            StackExchange.prepareEditor({
            heartbeatType: 'answer',
            autoActivateHeartbeat: false,
            convertImagesToLinks: false,
            noModals: true,
            showLowRepImageUploadWarning: true,
            reputationToPostImages: null,
            bindNavPrevention: true,
            postfix: "",
            imageUploader: {
            brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
            contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
            allowUrls: true
            },
            onDemand: true,
            discardSelector: ".discard-answer"
            ,immediatelyShowMarkdownHelp:true
            });


            }
            });














            draft saved

            draft discarded


















            StackExchange.ready(
            function () {
            StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f204644%2fcircular-cyclic-buffer-implementation%23new-answer', 'question_page');
            }
            );

            Post as a guest















            Required, but never shown

























            2 Answers
            2






            active

            oldest

            votes








            2 Answers
            2






            active

            oldest

            votes









            active

            oldest

            votes






            active

            oldest

            votes









            3














            Your ringbuffer has a compile time size, so it would be appropriate to make it a template class rather than simply passing it as an argument to the constructor. You never plan to change it anyway?



            template<size_t bufSize>
            class CircularBuffer


            That leads to the next point. You use a std::unique_ptr<int> There is something in C++ that is much better than this std::array. With the size of the buffer a compile time template argument of the class you can easily use it



            template<size_t bufSize>
            class CircularBuffer {
            std::array<int, bufSize> buf;
            std::size_t head;
            std::size_t tail;
            bool full_v;

            ...
            };


            Note that this increases the size of the class object considerably, as the std::array is directly inlined in the class.



            The other members of this class are always the same after construction so you should use static member initialization:



            template<size_t bufSize>
            class CircularBuffer {
            std::array<int, bufSize> buf;
            std::size_t head{ 0 };
            std::size_t tail{ 0 };
            bool full_v{ false };

            ...
            };


            Note that this completely removes the need to define a constructor at all. The compiler generated default constructor will do just fine.



            Resetting the buffer should not change the data stored in it as it is not read anyway so just leave it alone



            void CircularBuffer::reset()
            {
            head = 0;
            tail = 0;
            full_v = false;
            }


            Can tail ever be larger than head? If not why are you checking it.






            share|improve this answer





















            • Thank You for the review. Imagine a situation where buffer is full and both head and tail are in the middle of the buffer. Then reading happens. The tail keeps increasing while head remains in its position. So, in this case, tail can be ahead of head.
              – skr_robo
              Sep 30 at 19:03
















            3














            Your ringbuffer has a compile time size, so it would be appropriate to make it a template class rather than simply passing it as an argument to the constructor. You never plan to change it anyway?



            template<size_t bufSize>
            class CircularBuffer


            That leads to the next point. You use a std::unique_ptr<int> There is something in C++ that is much better than this std::array. With the size of the buffer a compile time template argument of the class you can easily use it



            template<size_t bufSize>
            class CircularBuffer {
            std::array<int, bufSize> buf;
            std::size_t head;
            std::size_t tail;
            bool full_v;

            ...
            };


            Note that this increases the size of the class object considerably, as the std::array is directly inlined in the class.



            The other members of this class are always the same after construction so you should use static member initialization:



            template<size_t bufSize>
            class CircularBuffer {
            std::array<int, bufSize> buf;
            std::size_t head{ 0 };
            std::size_t tail{ 0 };
            bool full_v{ false };

            ...
            };


            Note that this completely removes the need to define a constructor at all. The compiler generated default constructor will do just fine.



            Resetting the buffer should not change the data stored in it as it is not read anyway so just leave it alone



            void CircularBuffer::reset()
            {
            head = 0;
            tail = 0;
            full_v = false;
            }


            Can tail ever be larger than head? If not why are you checking it.






            share|improve this answer





















            • Thank You for the review. Imagine a situation where buffer is full and both head and tail are in the middle of the buffer. Then reading happens. The tail keeps increasing while head remains in its position. So, in this case, tail can be ahead of head.
              – skr_robo
              Sep 30 at 19:03














            3












            3








            3






            Your ringbuffer has a compile time size, so it would be appropriate to make it a template class rather than simply passing it as an argument to the constructor. You never plan to change it anyway?



            template<size_t bufSize>
            class CircularBuffer


            That leads to the next point. You use a std::unique_ptr<int> There is something in C++ that is much better than this std::array. With the size of the buffer a compile time template argument of the class you can easily use it



            template<size_t bufSize>
            class CircularBuffer {
            std::array<int, bufSize> buf;
            std::size_t head;
            std::size_t tail;
            bool full_v;

            ...
            };


            Note that this increases the size of the class object considerably, as the std::array is directly inlined in the class.



            The other members of this class are always the same after construction so you should use static member initialization:



            template<size_t bufSize>
            class CircularBuffer {
            std::array<int, bufSize> buf;
            std::size_t head{ 0 };
            std::size_t tail{ 0 };
            bool full_v{ false };

            ...
            };


            Note that this completely removes the need to define a constructor at all. The compiler generated default constructor will do just fine.



            Resetting the buffer should not change the data stored in it as it is not read anyway so just leave it alone



            void CircularBuffer::reset()
            {
            head = 0;
            tail = 0;
            full_v = false;
            }


            Can tail ever be larger than head? If not why are you checking it.






            share|improve this answer












            Your ringbuffer has a compile time size, so it would be appropriate to make it a template class rather than simply passing it as an argument to the constructor. You never plan to change it anyway?



            template<size_t bufSize>
            class CircularBuffer


            That leads to the next point. You use a std::unique_ptr<int> There is something in C++ that is much better than this std::array. With the size of the buffer a compile time template argument of the class you can easily use it



            template<size_t bufSize>
            class CircularBuffer {
            std::array<int, bufSize> buf;
            std::size_t head;
            std::size_t tail;
            bool full_v;

            ...
            };


            Note that this increases the size of the class object considerably, as the std::array is directly inlined in the class.



            The other members of this class are always the same after construction so you should use static member initialization:



            template<size_t bufSize>
            class CircularBuffer {
            std::array<int, bufSize> buf;
            std::size_t head{ 0 };
            std::size_t tail{ 0 };
            bool full_v{ false };

            ...
            };


            Note that this completely removes the need to define a constructor at all. The compiler generated default constructor will do just fine.



            Resetting the buffer should not change the data stored in it as it is not read anyway so just leave it alone



            void CircularBuffer::reset()
            {
            head = 0;
            tail = 0;
            full_v = false;
            }


            Can tail ever be larger than head? If not why are you checking it.







            share|improve this answer












            share|improve this answer



            share|improve this answer










            answered Sep 30 at 18:55









            miscco

            3,427517




            3,427517












            • Thank You for the review. Imagine a situation where buffer is full and both head and tail are in the middle of the buffer. Then reading happens. The tail keeps increasing while head remains in its position. So, in this case, tail can be ahead of head.
              – skr_robo
              Sep 30 at 19:03


















            • Thank You for the review. Imagine a situation where buffer is full and both head and tail are in the middle of the buffer. Then reading happens. The tail keeps increasing while head remains in its position. So, in this case, tail can be ahead of head.
              – skr_robo
              Sep 30 at 19:03
















            Thank You for the review. Imagine a situation where buffer is full and both head and tail are in the middle of the buffer. Then reading happens. The tail keeps increasing while head remains in its position. So, in this case, tail can be ahead of head.
            – skr_robo
            Sep 30 at 19:03




            Thank You for the review. Imagine a situation where buffer is full and both head and tail are in the middle of the buffer. Then reading happens. The tail keeps increasing while head remains in its position. So, in this case, tail can be ahead of head.
            – skr_robo
            Sep 30 at 19:03













            3














            Correctness and design



            I will skip all points miscco
            already raised and go directly to void write and int read.




            • Your write overwrites old values when the buffer is full.

            • Your read returns zero if the buffer is empty.


            Although understandable, it should at least be documented:



            /// Describe the class, what is the purpose, what is important
            template<class T, size_t N> // see std::array
            class CircularBuffer {
            ...
            /// Write values into the buffer, overwrite old values when the buffer is full
            void write(const T& data) {


            or maybe:



                /// Write values into the buffer
            ///return true if written, false if buffer was full
            bool write(const T& data) {


            You can also notice that I made it a template, just like std::array - both the type and the capacity are now template parameters. The standard way of passing arguments to methods like write is to use const T&, but it has some drawbacks, so, if it trully is for embedded systems, then simple T is an option as well (knowing you are going to use it with integers and such). But document it (and/or add other template parameters with some defaults).



            Synchronization (Locking)



            If you are going to use it with multiple threads (or main loop vs. interrupt) then some form of synchronization is indeed needed, because there are possible race-conditions otherwise, like one thread being inside write, just overwriting the oldest slot, while another thread can be inside read getting this newest element, while it should get the oldest, but tail was not yet updated by the first thread.



            First question to ask is: How many threads / concurrent readers and writers (producers and consumers)?
            Generic solution could use mutex but there can be better solutions for single producer single consumer (e.g. main loop vs. interrupt - USART and similar communication synchronization). One possibility is to use separate (atomic) write and read counters (like head and tail but you always only increase the counters and substract them to get number of elements inside the buffer - but be careful with order of operations, it is not trivial).






            share|improve this answer




























              3














              Correctness and design



              I will skip all points miscco
              already raised and go directly to void write and int read.




              • Your write overwrites old values when the buffer is full.

              • Your read returns zero if the buffer is empty.


              Although understandable, it should at least be documented:



              /// Describe the class, what is the purpose, what is important
              template<class T, size_t N> // see std::array
              class CircularBuffer {
              ...
              /// Write values into the buffer, overwrite old values when the buffer is full
              void write(const T& data) {


              or maybe:



                  /// Write values into the buffer
              ///return true if written, false if buffer was full
              bool write(const T& data) {


              You can also notice that I made it a template, just like std::array - both the type and the capacity are now template parameters. The standard way of passing arguments to methods like write is to use const T&, but it has some drawbacks, so, if it trully is for embedded systems, then simple T is an option as well (knowing you are going to use it with integers and such). But document it (and/or add other template parameters with some defaults).



              Synchronization (Locking)



              If you are going to use it with multiple threads (or main loop vs. interrupt) then some form of synchronization is indeed needed, because there are possible race-conditions otherwise, like one thread being inside write, just overwriting the oldest slot, while another thread can be inside read getting this newest element, while it should get the oldest, but tail was not yet updated by the first thread.



              First question to ask is: How many threads / concurrent readers and writers (producers and consumers)?
              Generic solution could use mutex but there can be better solutions for single producer single consumer (e.g. main loop vs. interrupt - USART and similar communication synchronization). One possibility is to use separate (atomic) write and read counters (like head and tail but you always only increase the counters and substract them to get number of elements inside the buffer - but be careful with order of operations, it is not trivial).






              share|improve this answer


























                3












                3








                3






                Correctness and design



                I will skip all points miscco
                already raised and go directly to void write and int read.




                • Your write overwrites old values when the buffer is full.

                • Your read returns zero if the buffer is empty.


                Although understandable, it should at least be documented:



                /// Describe the class, what is the purpose, what is important
                template<class T, size_t N> // see std::array
                class CircularBuffer {
                ...
                /// Write values into the buffer, overwrite old values when the buffer is full
                void write(const T& data) {


                or maybe:



                    /// Write values into the buffer
                ///return true if written, false if buffer was full
                bool write(const T& data) {


                You can also notice that I made it a template, just like std::array - both the type and the capacity are now template parameters. The standard way of passing arguments to methods like write is to use const T&, but it has some drawbacks, so, if it trully is for embedded systems, then simple T is an option as well (knowing you are going to use it with integers and such). But document it (and/or add other template parameters with some defaults).



                Synchronization (Locking)



                If you are going to use it with multiple threads (or main loop vs. interrupt) then some form of synchronization is indeed needed, because there are possible race-conditions otherwise, like one thread being inside write, just overwriting the oldest slot, while another thread can be inside read getting this newest element, while it should get the oldest, but tail was not yet updated by the first thread.



                First question to ask is: How many threads / concurrent readers and writers (producers and consumers)?
                Generic solution could use mutex but there can be better solutions for single producer single consumer (e.g. main loop vs. interrupt - USART and similar communication synchronization). One possibility is to use separate (atomic) write and read counters (like head and tail but you always only increase the counters and substract them to get number of elements inside the buffer - but be careful with order of operations, it is not trivial).






                share|improve this answer














                Correctness and design



                I will skip all points miscco
                already raised and go directly to void write and int read.




                • Your write overwrites old values when the buffer is full.

                • Your read returns zero if the buffer is empty.


                Although understandable, it should at least be documented:



                /// Describe the class, what is the purpose, what is important
                template<class T, size_t N> // see std::array
                class CircularBuffer {
                ...
                /// Write values into the buffer, overwrite old values when the buffer is full
                void write(const T& data) {


                or maybe:



                    /// Write values into the buffer
                ///return true if written, false if buffer was full
                bool write(const T& data) {


                You can also notice that I made it a template, just like std::array - both the type and the capacity are now template parameters. The standard way of passing arguments to methods like write is to use const T&, but it has some drawbacks, so, if it trully is for embedded systems, then simple T is an option as well (knowing you are going to use it with integers and such). But document it (and/or add other template parameters with some defaults).



                Synchronization (Locking)



                If you are going to use it with multiple threads (or main loop vs. interrupt) then some form of synchronization is indeed needed, because there are possible race-conditions otherwise, like one thread being inside write, just overwriting the oldest slot, while another thread can be inside read getting this newest element, while it should get the oldest, but tail was not yet updated by the first thread.



                First question to ask is: How many threads / concurrent readers and writers (producers and consumers)?
                Generic solution could use mutex but there can be better solutions for single producer single consumer (e.g. main loop vs. interrupt - USART and similar communication synchronization). One possibility is to use separate (atomic) write and read counters (like head and tail but you always only increase the counters and substract them to get number of elements inside the buffer - but be careful with order of operations, it is not trivial).







                share|improve this answer














                share|improve this answer



                share|improve this answer








                edited Dec 16 at 15:53









                albert

                1372




                1372










                answered Oct 1 at 12:41









                firda

                2,852627




                2,852627






























                    draft saved

                    draft discarded




















































                    Thanks for contributing an answer to Code Review Stack Exchange!


                    • Please be sure to answer the question. Provide details and share your research!

                    But avoid



                    • Asking for help, clarification, or responding to other answers.

                    • Making statements based on opinion; back them up with references or personal experience.


                    Use MathJax to format equations. MathJax reference.


                    To learn more, see our tips on writing great answers.





                    Some of your past answers have not been well-received, and you're in danger of being blocked from answering.


                    Please pay close attention to the following guidance:


                    • Please be sure to answer the question. Provide details and share your research!

                    But avoid



                    • Asking for help, clarification, or responding to other answers.

                    • Making statements based on opinion; back them up with references or personal experience.


                    To learn more, see our tips on writing great answers.




                    draft saved


                    draft discarded














                    StackExchange.ready(
                    function () {
                    StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f204644%2fcircular-cyclic-buffer-implementation%23new-answer', 'question_page');
                    }
                    );

                    Post as a guest















                    Required, but never shown





















































                    Required, but never shown














                    Required, but never shown












                    Required, but never shown







                    Required, but never shown

































                    Required, but never shown














                    Required, but never shown












                    Required, but never shown







                    Required, but never shown







                    Popular posts from this blog

                    Сан-Квентин

                    Алькесар

                    Josef Freinademetz