zoukankan      html  css  js  c++  java
  • Best way of implementing a circular buffer

    I wanted to implement a circular buffer for learning purpose only.

    My first option was to use a secondary status for rear and front pointers: (Like the ones I've seen in many websites)

    #include <iostream>
    using namespace std;
    
    template<class T>
    class ql
    {
        public:
            ql(int size)
            {
                this->size = size;
                data = new T[size];
                front = NULL;
                rear = NULL;
            }
    
            ~ql()
            {
                delete[] data;
            }
    
            void enQueue(T item)
            {
                T *nf = nextPtr(rear);
    
                if (nf != front)
                {
                    if (front == NULL)
                        front = &data[0];
    
                    *nf = item;
                    rear = nf;
                    cout << item << " Added. ^_^" << endl;
                }
                else
                    cout << "OverFLO@#R$MR... X_X" << endl;
            }
    
            T *deQueue()
            {
                if (rear != NULL)
                {
                    T *p = front;
                    if (front == rear)
                    {
                        front = NULL;
                        rear = NULL;
                    } 
                    else
                        front = nextPtr(front);
                    cout << *p << " is going to be returned. -_-" << endl;
                    return p;
                }
                else
                    cout << "Empty... >_<" << endl;
            }
        private:
            T *nextPtr(T *p)
            {
                if (p == &data[size - 1] || p == NULL)
                    return &data[0];
                return p + 1;
            }
    
            T *data, *rear, *front;
            int size;
    };
    
    int main()
    {
        ql<int> q(3);
    
        q.enQueue(1);
        q.enQueue(2);
        q.enQueue(3);
        q.enQueue(4);
    
        cout << endl;
        q.deQueue();
        q.deQueue();
        q.deQueue();
        q.deQueue();
    
        cout << endl;
        q.enQueue(5);
        q.enQueue(6);
    
        cout << endl;
        q.deQueue();
        q.deQueue();
        q.deQueue();
    
        return 0;
    }
    

    My second option was to sacrifice a space for the sake of distinguishing between empty and full circular buffers: (I saw this one on Ellis's Fundamentals of data structures)

    template<class T>
    class ql
    {
        public:
            ql(int size)
            {
                this->size = size;
                data = new T[size];
                front = 1;
                rear = 0;
            }
    
            ~ql()
            {
                delete[] data;
            }
    
            void enQueue(T item)
            {
                if ((rear + 2) % size != front)
                {
                    rear = (rear + 1) % size;
                    data[rear] = item;
                    cout << item << " Added. ^_^" << endl;
                }
                else
                    cout << "OverFLO@#R$MR... X_X" << endl;
            }
    
            T *deQueue()
            {
                if ((rear + 1) % size != front)
                {
                    T *p = &data[front];
                    cout << *p << " is going to be returned. -_-" << endl;
                    front = (front + 1) % size;
                    return p;
                }
                else
                    cout << "Empty... >_<" << endl;
            }
        private:
            T *data;
            int size, rear, front;
    };
    

    and my last option was to use another variable for storing used space in circular buffer:

    template<class T>
    class ql
    {
        public:
            ql(int size)
            {
                this->size = size;
                data = new T[size];
                buffer = 0;
                front = 1;
                rear = 0;
            }
    
            ~ql()
            {
                delete[] data;
            }
    
            void enQueue(T item)
            {
                if (buffer != size)
                {
                    buffer++;
                    rear = (rear + 1) % size;
                    data[rear] = item;
                    cout << item << " Added. ^_^" << endl;
                }
                else
                    cout << "OverFLO@#R$MR... X_X" << endl;
            }
    
            T *deQueue()
            {
                if (buffer != 0)
                {
                    buffer--;
                    T *p = &data[front];
                    cout << *p << " is going to be returned. -_-" << endl;
                    front = (front + 1) % size;
                    return p;
                }
                else
                    cout << "Empty... >_<" << endl;
            }
        private:
            T *data;
            int size, buffer, rear, front;
    };
    

    Which one of this approaches do you think is the best? I'm also looking for advises on how to change this class for practical using. thanks

    Use better names and do not use using namespace in headers

    The name q1 is rather arbitrary. queue or circular_queue is a lot better. That, by the way, is a perfect example why you shouldn't use using namespace std; when you write a header. There's already std::queue, so a queue would conflict with std::queue.

    Since you're writing a template class all your code will reside in a header at some point, so using namespace is out of the question either way.

    Use a smarter data store

    Use std::vector<T> or std::deque<T> instead of raw pointers for the memory. Or re-use std::queue<T>, unless you want to practice writing a queue completely by hand.

    Use return instead of cout

    Instead of cout << … << return bool or a custom enum in enQueue. If I want to store elements in a circular buffer, I need to know whether enQueue worked. I cannot check stdout for error messages.

    Sizes are positive

    Use size_t for sizes, not int.

    Check all return paths

    Return nullptr (C++11 or higher) or 0 in deQueue if the queue is empty. However, a pointer at that point is dangerous: the user must make a copy at some point, or they might end up with another object. Use std::optional if you have C++17 at hand instead, or

     bool deQueue(T & dest) {
         if(…) {
             // queue has elements
             dest = …;
             …
             return true;
         } else {
             // queue has no elements
             return false;
         }
     }
    

    enQueue could use a const T& instead of a T, by the way. Or you could use std::move for movable types.

    All at once

    If you follow these guidelines, you will end up

    #include <optional>
    #include <queue>
    #include <utility>
    
    template<class T>
    class queue
    {
        public:
            explicit queue(size_t size) : m_size(size) { }
    
            queue(const queue<T> & other) = default;
            queue(queue<T> && other) = default;
            queue& operator=(const queue<T> & other) = default;
            queue& operator=(queue<T> && other) = default;
    
            bool enQueue(T item)
            {
                if(m_data.size() == m_size) {
                    return false;
                } else {
                    m_data.push(std::move(item));
                    return true;
                }
            }
    
            std::optional<T> deQueue()
            {
                if(m_data.empty()) {
                    return std::nullopt;
                } else {
                    std::optional<T> result = m_data.front();
                    m_data.pop();
                    return result;
                }
            }
    
            size_t capacity() const
            {
                return m_size;
            }
    
            size_t size() const
            {
                return m_data.size();
            }
        private:
            size_t m_size;
            std::queue<T> m_data;
    };
    

    If you don't want to re-use std::queue or std::deque, I'd go with std::vector and your third approach.

    Congratulations on your first approach, by the way. I've seen raw-pointer usage going wrong too many times, and it's refreshing to see some clever use there. Well done. But that's more or less the way you would do it in C (sans template and class, of course).

    But you will probably admit that working with pointers at that point is somewhat a headache. Both front and rear (as pointers) can be expressed as data + x and data + y for two suitable int x and y, like you did in your second approach.

    Either way, unless you really need to use raw pointers use either smart pointers (e.g. std::unique_ptr<T[]>) or (better) full containers like std::vector.

    copyright

    https://codereview.stackexchange.com/questions/180556/best-way-of-implementing-a-circular-buffer

  • 相关阅读:
    Python列表List增删改查、拷贝
    面向对象之继承——python篇
    面向对象——python篇
    异常——python基础篇
    函数进阶
    文件操作——python基础篇
    函数(一)——python基础篇
    字典——python基础篇
    特有的字符串格式化函数format——python篇
    webdriver对应谷歌版本下载地址
  • 原文地址:https://www.cnblogs.com/dong1/p/14339379.html
Copyright © 2011-2022 走看看