Skip to main content

Stack Exchange Network

Stack Exchange network consists of 183 Q&A communities including Stack Overflow, the largest, most trusted online community for developers to learn, share their knowledge, and build their careers.

Visit Stack Exchange
Asked
Viewed 95 times
3
\$\begingroup\$

Looking to get some feedback on a simple non-multithreaded circular buffer.

Ideally, I'd want to have a logic to prevent the data from getting overwritten but couldn't find a reasonable way. For instance, if the buffer size is 3 and Write is invoked, it shouldn't proceed until the buffer is no longer full.

class Fifo
{
    public:
    void Write(uint8_t data)
    {   
        mBuffer[mWriteIdx++] = data;
        mWriteIdx %= BUFFER_SIZE;
        mIsFull = mWriteIdx == mReadIdx;
    }
    
    uint8_t Read() 
    {
        if (IsEmpty())
        {
            return 0; // considering 0 is an invalid data
        }
        
        uint8_t value = mBuffer[mReadIdx];
        
        mBuffer[mReadIdx++] = 0;   // null out the index
        mReadIdx %= BUFFER_SIZE;
        mIsFull = false;
        
        return value;
    }
    
    
    private:
    
    bool IsEmpty() const
    {
        return !mIsFull && mReadIdx == mWriteIdx;
    }
    
    // Member variables
    int mReadIdx = 0;
    int mWriteIdx = 0;
    
    static constexpr int BUFFER_SIZE = 4;
    uint8_t mBuffer[BUFFER_SIZE] = {0};
    bool mIsFull = false;
};

Here's a sample program with outputs

\$\endgroup\$

2 Answers 2

2
\$\begingroup\$

Advice I: IsEmpty

bool IsEmpty() const
    {
        return !mIsFull && mReadIdx == mWriteIdx;
    }

Why not deploy a field

size_t size = 0;

counting the number of elements in the buffer? That way, your IsEmpty becomes much simpler:

bool IsEmpty() const 
{
    return size == 0;
}

Advice II: Add IsFull How about this?

bool IsFull() const {
    return size == BUFFER_SIZE;
}

Advice III: Throwing appropriate exceptions

I would throw an std::underflow_error when trying to read from an empty buffer, and I would throw an std::overflow_error when trying to write to a full buffer.

Summa summarum

All in all, I thought about this implementation:

#include <cstdlib>
#include <cstdint>
#include <stdexcept>
#include <iostream>

class Fifo
{
public:
    void Write(uint8_t data)
    {
        if (IsFull())
        {
            throw std::overflow_error("The buffer is full.");
        }

        mBuffer[(headIndex + size++) % BUFFER_SIZE] = data;
    }

    uint8_t Read()
    {
        if (IsEmpty())
        {
            throw std::underflow_error("The buffer is empty.");
        }

        uint8_t value = mBuffer[headIndex++];
        headIndex %= BUFFER_SIZE;
        size--;
        return value;
    }


private:

    bool IsEmpty() const
    {
        return size == 0;
    }

    bool IsFull() const {
        return size == BUFFER_SIZE;
    }

    // Member variables
    size_t headIndex = 0;

    static constexpr int BUFFER_SIZE = 4;
    uint8_t mBuffer[BUFFER_SIZE] = { 0 };
    size_t size = 0;
};

int main()
{
    Fifo fifo;

    for (int i = 0; i < 4; i++)
    {
        fifo.Write(i + 2);
    }

    for (int i = 0; i < 4; i++)
    {
        std::cout << unsigned(fifo.Read()) << "\n";
    }

    fifo.Write(10);
    return 0;
}

Hope that helps.

\$\endgroup\$
6
  • \$\begingroup\$ I think you misspelt std::uint8_t throughout. \$\endgroup\$
    Toby Speight
    –  Toby Speight
    2023-06-02 10:31:30 +00:00
    Commented Jun 2, 2023 at 10:31
  • 1
    \$\begingroup\$ std::underflow_error and std::overflow_error are not appropriate exceptions. They're used to indicate floating point underflow or numeric overflow. The right exception would be std::logic_error. However, exceptions are also not appropriate in general because they're only meant to be used in exceptional situations, and attempting to push to a ring buffer and checking whether this succeeded is a common operation. Simply returning bool would make more sense. \$\endgroup\$
    Jan Schultke
    –  Jan Schultke
    2023-06-08 14:41:36 +00:00
    Commented Jun 8, 2023 at 14:41
  • \$\begingroup\$ @JanSchultke Agreed. I will update my answer tomorrow. \$\endgroup\$
    coderodde
    –  coderodde
    2023-06-09 18:19:23 +00:00
    Commented Jun 9, 2023 at 18:19
  • \$\begingroup\$ Thanks. Instead of throwing an exception if the buffer is full, is there a better way to approach it such that the requested data is 'not lost' and is rather queued as soon as there's space, it gets pushed into FIFO? \$\endgroup\$
    xyf
    –  xyf
    2023-06-11 23:11:30 +00:00
    Commented Jun 11, 2023 at 23:11
  • \$\begingroup\$ @xyf Basically, that's the same as having a larger buffer. \$\endgroup\$
    coderodde
    –  coderodde
    2023-06-12 06:14:59 +00:00
    Commented Jun 12, 2023 at 6:14
1
\$\begingroup\$

Use of the type uint8_t suggests that you're either including the C-compatibility header <stdint.h> or have a using at global scope.

Don't do either of those things. Include the C++ header <cstdint> and use the type it declares: std::uint8_t.

You might want to have

using value_type = std::uint8_t;

within the class. That will be helpful if you make it a template in future.


I would have liked to see a full set of unit tests for this class - it's perfectly suited to such testing.

\$\endgroup\$

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.

Morty Proxy This is a proxified and sanitized view of the page, visit original site.