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

  • 相关阅读:
    struts2+ajax实现异步刷新
    ajax实现异步刷新
    AJAX学习总结
    Java中的日期工具类
    jsp页面中用户注销的写法
    封神台靶场:第七章:GET THE PASS! 【技能点:进程中抓下管理员明文密码】
    封神台靶场:第六章:SYSTEM!POWER!【配套课时:webshel​​l控制目标实战演练】
    封神台靶场:第五章:进击!拿到Web最高权限!【配套课时:绕过防护上传木马实战演练】
    封神台靶场:第四章:为了更多的权限!留言板!【配套课时:cookie伪造目标权限 实战演练】
    封神台靶场:第三章:爆破管理员账户登录后台【配套课时:burp到支付和暴破 实战演练】
  • 原文地址:https://www.cnblogs.com/dong1/p/14339379.html
Copyright © 2011-2022 走看看