2
\$\begingroup\$

This class is for fast and small footprint buffering made specially for network I/O. It is similar to so-called circular buffers, but is not circular.

#ifndef _IO_BUFFER_H
#define _IO_BUFFER_H

#include <cstdint>
#include <cstring>
#include <cstdio>

namespace net{

template<size_t CAPACITY>
class IOBuffer{
public:
    using size_type = uint16_t;

private:
    size_type   head_   = 0;
    size_type   tail_   = 0;
    char        buffer_[CAPACITY];

public:
    void clear(){
        head_ = 0;
        tail_ = 0;
    }

    constexpr
    static size_t max_size(){
        return CAPACITY;
    }

    // ==================================

    const char *data() const{
        return & buffer_[head_];
    }

    char *dataTail(){
        return & buffer_[tail_];
    }

    size_t size() const{
        return tail_ - head_;
    }

    size_t capacity() const{
        return CAPACITY - tail_;
    }

    // ==================================

    bool push(const char *p){
        return p ? push(strlen(p), p) : false;
    }

    bool push(size_t const len, const void *ptr = nullptr){
        if (len == 0)
            return false;

        if (capacity() < len)
            return false;

        if (ptr)
            memmove(&buffer_[tail_], ptr, len);

        tail_ = (size_type) (tail_ + len);

        return true;
    }

    bool pop(size_t const len){
        if (len == 0)
            return false;

        auto const available = size();

        if (available < len)
            return false;

        if (available == len){
            clear();
            return true;
        }

        head_ = (size_type) (head_ + len);

        return true;
    }

    // ==================================

    void print() const{
        printf("h: %3u | t: %3u | ad: %3zu | ac: %3zu | %.*s\n",
                head_, tail_,
                size(), capacity(),
                (int) size(), buffer_ );
    }
};

} // namespace

#endif

Usual usage include several socket read() until protocol data is collected, then several write() until response is sent back.

Reads and writes will not be mixed, but for the moment this is responsibility of the caller.

print is debug method, non intended to be called.

int main(){
    using Buffer = net::IOBuffer<10>;

    Buffer b;
    b.print();

    b.push("aaa");
    b.print();

    b.push("bbb");
    b.print();

    b.push("ccc");
    b.print();

    b.push("xxx");
    b.print();

    b.push("d");
    b.print();

    b.push("xxx");
    b.print();

    b.pop(3);
    b.print();

    b.pop(3);
    b.print();

    b.pop(b.size()); // same as b.clear()
    b.print();
}
\$\endgroup\$
3
  • \$\begingroup\$ There seem to be some typos in the code. At least i dont understand why for example size returns a size_t rather than a size_type. \$\endgroup\$
    – miscco
    Commented Aug 31, 2016 at 5:02
  • \$\begingroup\$ idea was to have smaller size size-type inside the class, but outside to be standard size-t \$\endgroup\$
    – Nick
    Commented Aug 31, 2016 at 5:19
  • \$\begingroup\$ What is the rationale for this class? Why is it better than a circular buffer in this application? \$\endgroup\$
    – Emily L.
    Commented Sep 29, 2016 at 20:50

2 Answers 2

1
\$\begingroup\$

One of the benefits of circular buffers is that they can be filled up to the entire size of the buffer, with completed fields being extracted from the head of the buffer immediately freeing up space. This isn't the case for your buffer. Space in the buffer is never freed until the entire buffer has been read, so if partial fields are consistently read there is a risk the buffer will get to a full state.

dataTail

You've declared your other access methods, including data as const. Is it a mistake to not declare dataTail as const? It's not entirely clear to me why a client would need to have a pointer to the bit of the buffer you haven't used. Or are you expecting the client to actually write to the dataTail, without amending the tail_ afterwards?!?

push

Your extended push seems like it has a bug (or allows a very strange use case):

bool push(size_t const len, const void *ptr = nullptr){
    if (len == 0)
        return false;

    if (capacity() < len)
        return false;

    if (ptr)
        memmove(&buffer_[tail_], ptr, len);

    tail_ = (size_type) (tail_ + len);

    return true;
}

If ptr is null, then no bytes are added to the buffer, however the tail is still incremented as if data had been written. So effectively, whatever is already in the buffer is added instead. Having ptr default to null suggests this might be expected behaviour but it seems very odd.

print

print may be a debug method, however its output is confusing. As you pop from the buffer, you're reducing the number of characters from the buffer that are displayed however you're still printing from the start of the buffer. This gives the impression that pop is actually removing from the tail, rather than the head. You should probably be printing from the head instead:

printf("h: %3u | t: %3u | ad: %3zu | ac: %3zu | %.*s\n",
        head_, tail_,
        size(), capacity(),
        (int) size(), data());
\$\endgroup\$
2
  • 1
    \$\begingroup\$ push() with null make space for "later" use. tail() is not const because it can be used to add data with push() . Because of my project, I did refactor the class and now I use std::string (works with std::vector too) for storage variable size data. \$\endgroup\$
    – Nick
    Commented Nov 29, 2016 at 15:18
  • 1
    \$\begingroup\$ @Nick Thanks for the extra context. I think I'd have been tempted to extract the null push case into a separate function, possibly reserve(size) to make its intention clearer. \$\endgroup\$
    – forsvarir
    Commented Nov 29, 2016 at 15:41
0
\$\begingroup\$

I got as far as this:

 bool push(const char *p){
            return push(p, strlen(p));
        }

So for every string you are iterating over its length. before adding it to the buffer. There must be a better way. Most of your strings are compile time arrays you can pull the size at compile time with a bit of work.

\$\endgroup\$
3
  • \$\begingroup\$ I understand what do you mean, but I have no idea how this can be fixed. Caller of course could use the other function with constexpr strlen() result. \$\endgroup\$
    – Nick
    Commented Apr 1, 2016 at 16:57
  • \$\begingroup\$ @Nick: Sorry was in a rush this morning. Will circle back later. \$\endgroup\$ Commented Apr 1, 2016 at 18:16
  • \$\begingroup\$ template<size_t N> bool push_c(const char (&p)[N]){ return push(N - 1, p); } \$\endgroup\$
    – Nick
    Commented Apr 2, 2016 at 8:03

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.