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();
}
-
\$\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– miscco2016年08月31日 05:02:59 +00:00Commented 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– Nick2016年08月31日 05:19:43 +00:00Commented 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.– Emily L.2016年09月29日 20:50:42 +00:00Commented Sep 29, 2016 at 20:50
2 Answers 2
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
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());
-
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– Nick2016年11月29日 15:18:15 +00:00Commented 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– forsvarir2016年11月29日 15:41:15 +00:00Commented Nov 29, 2016 at 15:41
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.
-
\$\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– Nick2016年04月01日 16:57:52 +00:00Commented Apr 1, 2016 at 16:57
-
\$\begingroup\$ @Nick: Sorry was in a rush this morning. Will circle back later. \$\endgroup\$Loki Astari– Loki Astari2016年04月01日 18:16:20 +00:00Commented 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– Nick2016年04月02日 08:03:33 +00:00Commented Apr 2, 2016 at 8:03