Elegant Circular Buffer
up vote
4
down vote
favorite
I would like to design an elegant yet efficient circular buffer of integers.
I have designed a FIFO type circular buffer in C++(11)- starting with the class header (declaration) as follow:
[cyclicBuffer.hpp]:
#ifndef __CyclicBuff_HPP
#define __CyclicBuff_HPP
#pragma once
class CyclicBuffer
{
public:
CyclicBuffer(int buffSize);
~CyclicBuffer();
void setChar(char c);
char getChar();
void clearCycBuff(int buffSize);
private:
char *buffer;
int bufferSize;
int head; // index for the top of the buffer
int tail; // indes for the bottom of the buffer
};
#endif
implementation of the class is as following:
[cyclicBuffer.cpp]:
#include "stdafx.h"
#include "cyclicBuffer.hpp"
#include <iostream>
//using namespace std;
CyclicBuffer::CyclicBuffer(int buffSize)
{
head = 0;
tail = 0;
bufferSize = buffSize;
buffer = new char[bufferSize];
std::cout << "Buffer initated successfullyn";
/*
{
std::cout << "Buffer was not createdn driver exiting...n";
// return -1;
}*/
}
CyclicBuffer::~CyclicBuffer()
{
delete buffer;
std::cout << "Buffer destroyed successfullyn";
}
void CyclicBuffer::clearCycBuff(int buffSize) //not used , has no meaning to set '0'
{
head = 0;
tail = 0;
for (int i = 0; i <= buffSize; i++)
buffer[i] = '0';
}
void CyclicBuffer::setChar(char c)
{
//std::cout << "head: " << head << 'n';
head %= bufferSize;
buffer[head] = c;
head++;
}
char CyclicBuffer::getChar(void)
{
//std::cout << " tail:" << tail<<'n';
tail %= bufferSize;
return buffer[tail++];
}
Finally, testing the class by an instance as follow:
// CyclicBufferMain.cpp : Defines the entry point for the console
// application.
#include "stdafx.h"
#include <iostream>
#include "cyclicBuffer.hpp"
#define SIZE 32
int main(void)
{
CyclicBuffer aBuffer(SIZE);
for (int i = 0; i < SIZE*1.5; i++)
aBuffer.setChar((char)(i+45));
for (int i = 0; i < SIZE ; i++)
std::cout << aBuffer.getChar() << ' ';
std::cout << 'n';
return 0;
}
for the given main function the result will be like this:
My question: is this class is efficient enough for low-rate uC ? (targeting to Atmel, RISC type or low performance ARM uC)
Regarding @Toby Speight: Thanks for your helpful comments and advices - few questions for you:
(1) when I did delete instead delete , did I make a 'memory leak'? why MS VStudio (2015) didn't warn me?
(2) I would change the CyclicBuffer::clearCycBuff() member function to use buffer[i] = 0; (instead buffer[i]='0';) to clear the buffer's values.
(3) Why need to drop the commented-out code?
(4) You used uniform initializer at the constructor - is it big difference as to what I did using regular variable assignments at the constructor ?
(5) I know 'size_t' is it a specialize typedef that exists at the GNU and MS compilers but would have a problem using it when porting the code to a specific target which could not have such typedef ?
Thanks.
c++ circular-list embedded
migrated from stackoverflow.com May 24 '17 at 20:08
This question came from our site for professional and enthusiast programmers.
|
show 3 more comments
up vote
4
down vote
favorite
I would like to design an elegant yet efficient circular buffer of integers.
I have designed a FIFO type circular buffer in C++(11)- starting with the class header (declaration) as follow:
[cyclicBuffer.hpp]:
#ifndef __CyclicBuff_HPP
#define __CyclicBuff_HPP
#pragma once
class CyclicBuffer
{
public:
CyclicBuffer(int buffSize);
~CyclicBuffer();
void setChar(char c);
char getChar();
void clearCycBuff(int buffSize);
private:
char *buffer;
int bufferSize;
int head; // index for the top of the buffer
int tail; // indes for the bottom of the buffer
};
#endif
implementation of the class is as following:
[cyclicBuffer.cpp]:
#include "stdafx.h"
#include "cyclicBuffer.hpp"
#include <iostream>
//using namespace std;
CyclicBuffer::CyclicBuffer(int buffSize)
{
head = 0;
tail = 0;
bufferSize = buffSize;
buffer = new char[bufferSize];
std::cout << "Buffer initated successfullyn";
/*
{
std::cout << "Buffer was not createdn driver exiting...n";
// return -1;
}*/
}
CyclicBuffer::~CyclicBuffer()
{
delete buffer;
std::cout << "Buffer destroyed successfullyn";
}
void CyclicBuffer::clearCycBuff(int buffSize) //not used , has no meaning to set '0'
{
head = 0;
tail = 0;
for (int i = 0; i <= buffSize; i++)
buffer[i] = '0';
}
void CyclicBuffer::setChar(char c)
{
//std::cout << "head: " << head << 'n';
head %= bufferSize;
buffer[head] = c;
head++;
}
char CyclicBuffer::getChar(void)
{
//std::cout << " tail:" << tail<<'n';
tail %= bufferSize;
return buffer[tail++];
}
Finally, testing the class by an instance as follow:
// CyclicBufferMain.cpp : Defines the entry point for the console
// application.
#include "stdafx.h"
#include <iostream>
#include "cyclicBuffer.hpp"
#define SIZE 32
int main(void)
{
CyclicBuffer aBuffer(SIZE);
for (int i = 0; i < SIZE*1.5; i++)
aBuffer.setChar((char)(i+45));
for (int i = 0; i < SIZE ; i++)
std::cout << aBuffer.getChar() << ' ';
std::cout << 'n';
return 0;
}
for the given main function the result will be like this:
My question: is this class is efficient enough for low-rate uC ? (targeting to Atmel, RISC type or low performance ARM uC)
Regarding @Toby Speight: Thanks for your helpful comments and advices - few questions for you:
(1) when I did delete instead delete , did I make a 'memory leak'? why MS VStudio (2015) didn't warn me?
(2) I would change the CyclicBuffer::clearCycBuff() member function to use buffer[i] = 0; (instead buffer[i]='0';) to clear the buffer's values.
(3) Why need to drop the commented-out code?
(4) You used uniform initializer at the constructor - is it big difference as to what I did using regular variable assignments at the constructor ?
(5) I know 'size_t' is it a specialize typedef that exists at the GNU and MS compilers but would have a problem using it when porting the code to a specific target which could not have such typedef ?
Thanks.
c++ circular-list embedded
migrated from stackoverflow.com May 24 '17 at 20:08
This question came from our site for professional and enthusiast programmers.
2
I have usually found an additional value to be necessary: the number of items in the buffer, because when the indexhead == tail
you cannot know whether a circular buffer is empty or full.
– Weather Vane
May 24 '17 at 19:44
1
... and sweeter, because you don't need to be continually making a(head - tail + size) % size
calculation to determine how many items there are in the buffer.
– Weather Vane
May 24 '17 at 19:53
2
The most heavy operation in your actual code is taking the remainder. Its performance varies target-wise, and you might find some performance gain by switching it to comparison with the container size. Also, restricting the possible buffer sizes to the power of two will allow you to substitute the division with bitwise and.
– iehrlich
May 24 '17 at 19:53
2
Also, you don't need both include guards and#pragma once
.
– iehrlich
May 24 '17 at 19:54
2
Don't forget to change yourdelete
todelete
– Ceros
May 24 '17 at 21:25
|
show 3 more comments
up vote
4
down vote
favorite
up vote
4
down vote
favorite
I would like to design an elegant yet efficient circular buffer of integers.
I have designed a FIFO type circular buffer in C++(11)- starting with the class header (declaration) as follow:
[cyclicBuffer.hpp]:
#ifndef __CyclicBuff_HPP
#define __CyclicBuff_HPP
#pragma once
class CyclicBuffer
{
public:
CyclicBuffer(int buffSize);
~CyclicBuffer();
void setChar(char c);
char getChar();
void clearCycBuff(int buffSize);
private:
char *buffer;
int bufferSize;
int head; // index for the top of the buffer
int tail; // indes for the bottom of the buffer
};
#endif
implementation of the class is as following:
[cyclicBuffer.cpp]:
#include "stdafx.h"
#include "cyclicBuffer.hpp"
#include <iostream>
//using namespace std;
CyclicBuffer::CyclicBuffer(int buffSize)
{
head = 0;
tail = 0;
bufferSize = buffSize;
buffer = new char[bufferSize];
std::cout << "Buffer initated successfullyn";
/*
{
std::cout << "Buffer was not createdn driver exiting...n";
// return -1;
}*/
}
CyclicBuffer::~CyclicBuffer()
{
delete buffer;
std::cout << "Buffer destroyed successfullyn";
}
void CyclicBuffer::clearCycBuff(int buffSize) //not used , has no meaning to set '0'
{
head = 0;
tail = 0;
for (int i = 0; i <= buffSize; i++)
buffer[i] = '0';
}
void CyclicBuffer::setChar(char c)
{
//std::cout << "head: " << head << 'n';
head %= bufferSize;
buffer[head] = c;
head++;
}
char CyclicBuffer::getChar(void)
{
//std::cout << " tail:" << tail<<'n';
tail %= bufferSize;
return buffer[tail++];
}
Finally, testing the class by an instance as follow:
// CyclicBufferMain.cpp : Defines the entry point for the console
// application.
#include "stdafx.h"
#include <iostream>
#include "cyclicBuffer.hpp"
#define SIZE 32
int main(void)
{
CyclicBuffer aBuffer(SIZE);
for (int i = 0; i < SIZE*1.5; i++)
aBuffer.setChar((char)(i+45));
for (int i = 0; i < SIZE ; i++)
std::cout << aBuffer.getChar() << ' ';
std::cout << 'n';
return 0;
}
for the given main function the result will be like this:
My question: is this class is efficient enough for low-rate uC ? (targeting to Atmel, RISC type or low performance ARM uC)
Regarding @Toby Speight: Thanks for your helpful comments and advices - few questions for you:
(1) when I did delete instead delete , did I make a 'memory leak'? why MS VStudio (2015) didn't warn me?
(2) I would change the CyclicBuffer::clearCycBuff() member function to use buffer[i] = 0; (instead buffer[i]='0';) to clear the buffer's values.
(3) Why need to drop the commented-out code?
(4) You used uniform initializer at the constructor - is it big difference as to what I did using regular variable assignments at the constructor ?
(5) I know 'size_t' is it a specialize typedef that exists at the GNU and MS compilers but would have a problem using it when porting the code to a specific target which could not have such typedef ?
Thanks.
c++ circular-list embedded
I would like to design an elegant yet efficient circular buffer of integers.
I have designed a FIFO type circular buffer in C++(11)- starting with the class header (declaration) as follow:
[cyclicBuffer.hpp]:
#ifndef __CyclicBuff_HPP
#define __CyclicBuff_HPP
#pragma once
class CyclicBuffer
{
public:
CyclicBuffer(int buffSize);
~CyclicBuffer();
void setChar(char c);
char getChar();
void clearCycBuff(int buffSize);
private:
char *buffer;
int bufferSize;
int head; // index for the top of the buffer
int tail; // indes for the bottom of the buffer
};
#endif
implementation of the class is as following:
[cyclicBuffer.cpp]:
#include "stdafx.h"
#include "cyclicBuffer.hpp"
#include <iostream>
//using namespace std;
CyclicBuffer::CyclicBuffer(int buffSize)
{
head = 0;
tail = 0;
bufferSize = buffSize;
buffer = new char[bufferSize];
std::cout << "Buffer initated successfullyn";
/*
{
std::cout << "Buffer was not createdn driver exiting...n";
// return -1;
}*/
}
CyclicBuffer::~CyclicBuffer()
{
delete buffer;
std::cout << "Buffer destroyed successfullyn";
}
void CyclicBuffer::clearCycBuff(int buffSize) //not used , has no meaning to set '0'
{
head = 0;
tail = 0;
for (int i = 0; i <= buffSize; i++)
buffer[i] = '0';
}
void CyclicBuffer::setChar(char c)
{
//std::cout << "head: " << head << 'n';
head %= bufferSize;
buffer[head] = c;
head++;
}
char CyclicBuffer::getChar(void)
{
//std::cout << " tail:" << tail<<'n';
tail %= bufferSize;
return buffer[tail++];
}
Finally, testing the class by an instance as follow:
// CyclicBufferMain.cpp : Defines the entry point for the console
// application.
#include "stdafx.h"
#include <iostream>
#include "cyclicBuffer.hpp"
#define SIZE 32
int main(void)
{
CyclicBuffer aBuffer(SIZE);
for (int i = 0; i < SIZE*1.5; i++)
aBuffer.setChar((char)(i+45));
for (int i = 0; i < SIZE ; i++)
std::cout << aBuffer.getChar() << ' ';
std::cout << 'n';
return 0;
}
for the given main function the result will be like this:
My question: is this class is efficient enough for low-rate uC ? (targeting to Atmel, RISC type or low performance ARM uC)
Regarding @Toby Speight: Thanks for your helpful comments and advices - few questions for you:
(1) when I did delete instead delete , did I make a 'memory leak'? why MS VStudio (2015) didn't warn me?
(2) I would change the CyclicBuffer::clearCycBuff() member function to use buffer[i] = 0; (instead buffer[i]='0';) to clear the buffer's values.
(3) Why need to drop the commented-out code?
(4) You used uniform initializer at the constructor - is it big difference as to what I did using regular variable assignments at the constructor ?
(5) I know 'size_t' is it a specialize typedef that exists at the GNU and MS compilers but would have a problem using it when porting the code to a specific target which could not have such typedef ?
Thanks.
c++ circular-list embedded
c++ circular-list embedded
edited Nov 12 at 12:49
Mathias Ettinger
23k33177
23k33177
asked May 24 '17 at 19:36
Itzik Chaimov
235
235
migrated from stackoverflow.com May 24 '17 at 20:08
This question came from our site for professional and enthusiast programmers.
migrated from stackoverflow.com May 24 '17 at 20:08
This question came from our site for professional and enthusiast programmers.
2
I have usually found an additional value to be necessary: the number of items in the buffer, because when the indexhead == tail
you cannot know whether a circular buffer is empty or full.
– Weather Vane
May 24 '17 at 19:44
1
... and sweeter, because you don't need to be continually making a(head - tail + size) % size
calculation to determine how many items there are in the buffer.
– Weather Vane
May 24 '17 at 19:53
2
The most heavy operation in your actual code is taking the remainder. Its performance varies target-wise, and you might find some performance gain by switching it to comparison with the container size. Also, restricting the possible buffer sizes to the power of two will allow you to substitute the division with bitwise and.
– iehrlich
May 24 '17 at 19:53
2
Also, you don't need both include guards and#pragma once
.
– iehrlich
May 24 '17 at 19:54
2
Don't forget to change yourdelete
todelete
– Ceros
May 24 '17 at 21:25
|
show 3 more comments
2
I have usually found an additional value to be necessary: the number of items in the buffer, because when the indexhead == tail
you cannot know whether a circular buffer is empty or full.
– Weather Vane
May 24 '17 at 19:44
1
... and sweeter, because you don't need to be continually making a(head - tail + size) % size
calculation to determine how many items there are in the buffer.
– Weather Vane
May 24 '17 at 19:53
2
The most heavy operation in your actual code is taking the remainder. Its performance varies target-wise, and you might find some performance gain by switching it to comparison with the container size. Also, restricting the possible buffer sizes to the power of two will allow you to substitute the division with bitwise and.
– iehrlich
May 24 '17 at 19:53
2
Also, you don't need both include guards and#pragma once
.
– iehrlich
May 24 '17 at 19:54
2
Don't forget to change yourdelete
todelete
– Ceros
May 24 '17 at 21:25
2
2
I have usually found an additional value to be necessary: the number of items in the buffer, because when the index
head == tail
you cannot know whether a circular buffer is empty or full.– Weather Vane
May 24 '17 at 19:44
I have usually found an additional value to be necessary: the number of items in the buffer, because when the index
head == tail
you cannot know whether a circular buffer is empty or full.– Weather Vane
May 24 '17 at 19:44
1
1
... and sweeter, because you don't need to be continually making a
(head - tail + size) % size
calculation to determine how many items there are in the buffer.– Weather Vane
May 24 '17 at 19:53
... and sweeter, because you don't need to be continually making a
(head - tail + size) % size
calculation to determine how many items there are in the buffer.– Weather Vane
May 24 '17 at 19:53
2
2
The most heavy operation in your actual code is taking the remainder. Its performance varies target-wise, and you might find some performance gain by switching it to comparison with the container size. Also, restricting the possible buffer sizes to the power of two will allow you to substitute the division with bitwise and.
– iehrlich
May 24 '17 at 19:53
The most heavy operation in your actual code is taking the remainder. Its performance varies target-wise, and you might find some performance gain by switching it to comparison with the container size. Also, restricting the possible buffer sizes to the power of two will allow you to substitute the division with bitwise and.
– iehrlich
May 24 '17 at 19:53
2
2
Also, you don't need both include guards and
#pragma once
.– iehrlich
May 24 '17 at 19:54
Also, you don't need both include guards and
#pragma once
.– iehrlich
May 24 '17 at 19:54
2
2
Don't forget to change your
delete
to delete
– Ceros
May 24 '17 at 21:25
Don't forget to change your
delete
to delete
– Ceros
May 24 '17 at 21:25
|
show 3 more comments
2 Answers
2
active
oldest
votes
up vote
3
down vote
accepted
Improve the wrap-around logic
There's obvious inefficiency in:
head %= bufferSize;
%
uses division, but because we're incrementing, we know that head / bufferSize
is at most 1
. Instead, we can:
if (++head >= bufferSize)
head -= bufferSize;
On architectures such as ARM, there's no branch here, as any decent compiler will just use a condition flag on the subtraction.
Consider checking for overrun and underrun
At the moment, this buffer has no checking for the read and write positions crossing each other. Perhaps that's what you need, but it's quite unusual to have an unchecked circular buffer, and if you were holding (e.g.) audio data, you can imagine how it would sound, given the output you show.
Match new
and delete
Since you create buffer
using new
, you must use the array form delete
in the destructor:
CyclicBuffer::~CyclicBuffer()
{
delete buffer;
}
Style points
Prefer initializers to assignment
A constructor should initialize all the members of the class:
CyclicBuffer::CyclicBuffer(std::size_t buffSize)
: buffer(new char[bufferSize]),
bufferSize(buffSize),
head(0),
tail(0)
{
}
A good compiler will warn you if you have uninitialized members (e.g. g++ -Wall -Wextra
).
Note that I've also changed the argument to be a size_t
, as a negative size is meaningless. The int
members should all be size_t
, too.
Avoid numeric character constants
What's special about the number 45
? If you care about its character value, (and if you're using an ASCII-like system), it's clearer to write '-'
instead.
Drop the commented-out code
I don't want to see using std::namespace;
, ever. Not even in a comment.
Regarding the suggested fix about%
division, since it is alwaysincrementing
, we can putif (++head >= bufferSize) head = 0;
right? And it should be more efficient as we are avoiding the subtraction. Am I missing something here?
– Anubis
Jun 28 at 10:55
No, you're missing nothing - assigning zero is less work than subtraction, and would be a further improvement.
– Toby Speight
Jul 4 at 7:20
2
@TobySpeight : In fact,//
is the best prefix forusing namespace std;
– Calak
Nov 12 at 14:57
add a comment |
up vote
0
down vote
Constructor initialization - 0 out the array
buffer(new T[size]{})
will set all the values to 0 instead of random garbage
template<class T>
CyclicBuffer<T>::CyclicBuffer(std::size_t size): head(0), tail(0), buffer(new T[size]{}), size(size) {
}
Adding and Getting can use equality comparison
Comparing equality rather than subtracting/greater or equals makes it more readable
template<class T>
void CyclicBuffer<T>::add(T val) {
if (head == size)
head = 0;
buffer[head++] = val;
}
template<class T>
T CyclicBuffer<T>::get() {
if (tail == size)
tail = 0;
return buffer[tail++];
}
Use template to make your structure generic
You will be able to insert any type:
CyclicBuffer<char> charCyclicBuffer(5);
CyclicBuffer<int> intCyclicBuffer(5);
Use std::size_t for portability
Besides portability - size, head, tail will never be negative
template<class T>
class CyclicBuffer{
public:
explicit CyclicBuffer(std::size_t size);
~CyclicBuffer();
void add(T val);
T get();
inline std::size_t getSize() {
return this->size;
}
private:
T *buffer;
std::size_t size;
std::size_t head;
std::size_t tail;
};
New contributor
add a comment |
2 Answers
2
active
oldest
votes
2 Answers
2
active
oldest
votes
active
oldest
votes
active
oldest
votes
up vote
3
down vote
accepted
Improve the wrap-around logic
There's obvious inefficiency in:
head %= bufferSize;
%
uses division, but because we're incrementing, we know that head / bufferSize
is at most 1
. Instead, we can:
if (++head >= bufferSize)
head -= bufferSize;
On architectures such as ARM, there's no branch here, as any decent compiler will just use a condition flag on the subtraction.
Consider checking for overrun and underrun
At the moment, this buffer has no checking for the read and write positions crossing each other. Perhaps that's what you need, but it's quite unusual to have an unchecked circular buffer, and if you were holding (e.g.) audio data, you can imagine how it would sound, given the output you show.
Match new
and delete
Since you create buffer
using new
, you must use the array form delete
in the destructor:
CyclicBuffer::~CyclicBuffer()
{
delete buffer;
}
Style points
Prefer initializers to assignment
A constructor should initialize all the members of the class:
CyclicBuffer::CyclicBuffer(std::size_t buffSize)
: buffer(new char[bufferSize]),
bufferSize(buffSize),
head(0),
tail(0)
{
}
A good compiler will warn you if you have uninitialized members (e.g. g++ -Wall -Wextra
).
Note that I've also changed the argument to be a size_t
, as a negative size is meaningless. The int
members should all be size_t
, too.
Avoid numeric character constants
What's special about the number 45
? If you care about its character value, (and if you're using an ASCII-like system), it's clearer to write '-'
instead.
Drop the commented-out code
I don't want to see using std::namespace;
, ever. Not even in a comment.
Regarding the suggested fix about%
division, since it is alwaysincrementing
, we can putif (++head >= bufferSize) head = 0;
right? And it should be more efficient as we are avoiding the subtraction. Am I missing something here?
– Anubis
Jun 28 at 10:55
No, you're missing nothing - assigning zero is less work than subtraction, and would be a further improvement.
– Toby Speight
Jul 4 at 7:20
2
@TobySpeight : In fact,//
is the best prefix forusing namespace std;
– Calak
Nov 12 at 14:57
add a comment |
up vote
3
down vote
accepted
Improve the wrap-around logic
There's obvious inefficiency in:
head %= bufferSize;
%
uses division, but because we're incrementing, we know that head / bufferSize
is at most 1
. Instead, we can:
if (++head >= bufferSize)
head -= bufferSize;
On architectures such as ARM, there's no branch here, as any decent compiler will just use a condition flag on the subtraction.
Consider checking for overrun and underrun
At the moment, this buffer has no checking for the read and write positions crossing each other. Perhaps that's what you need, but it's quite unusual to have an unchecked circular buffer, and if you were holding (e.g.) audio data, you can imagine how it would sound, given the output you show.
Match new
and delete
Since you create buffer
using new
, you must use the array form delete
in the destructor:
CyclicBuffer::~CyclicBuffer()
{
delete buffer;
}
Style points
Prefer initializers to assignment
A constructor should initialize all the members of the class:
CyclicBuffer::CyclicBuffer(std::size_t buffSize)
: buffer(new char[bufferSize]),
bufferSize(buffSize),
head(0),
tail(0)
{
}
A good compiler will warn you if you have uninitialized members (e.g. g++ -Wall -Wextra
).
Note that I've also changed the argument to be a size_t
, as a negative size is meaningless. The int
members should all be size_t
, too.
Avoid numeric character constants
What's special about the number 45
? If you care about its character value, (and if you're using an ASCII-like system), it's clearer to write '-'
instead.
Drop the commented-out code
I don't want to see using std::namespace;
, ever. Not even in a comment.
Regarding the suggested fix about%
division, since it is alwaysincrementing
, we can putif (++head >= bufferSize) head = 0;
right? And it should be more efficient as we are avoiding the subtraction. Am I missing something here?
– Anubis
Jun 28 at 10:55
No, you're missing nothing - assigning zero is less work than subtraction, and would be a further improvement.
– Toby Speight
Jul 4 at 7:20
2
@TobySpeight : In fact,//
is the best prefix forusing namespace std;
– Calak
Nov 12 at 14:57
add a comment |
up vote
3
down vote
accepted
up vote
3
down vote
accepted
Improve the wrap-around logic
There's obvious inefficiency in:
head %= bufferSize;
%
uses division, but because we're incrementing, we know that head / bufferSize
is at most 1
. Instead, we can:
if (++head >= bufferSize)
head -= bufferSize;
On architectures such as ARM, there's no branch here, as any decent compiler will just use a condition flag on the subtraction.
Consider checking for overrun and underrun
At the moment, this buffer has no checking for the read and write positions crossing each other. Perhaps that's what you need, but it's quite unusual to have an unchecked circular buffer, and if you were holding (e.g.) audio data, you can imagine how it would sound, given the output you show.
Match new
and delete
Since you create buffer
using new
, you must use the array form delete
in the destructor:
CyclicBuffer::~CyclicBuffer()
{
delete buffer;
}
Style points
Prefer initializers to assignment
A constructor should initialize all the members of the class:
CyclicBuffer::CyclicBuffer(std::size_t buffSize)
: buffer(new char[bufferSize]),
bufferSize(buffSize),
head(0),
tail(0)
{
}
A good compiler will warn you if you have uninitialized members (e.g. g++ -Wall -Wextra
).
Note that I've also changed the argument to be a size_t
, as a negative size is meaningless. The int
members should all be size_t
, too.
Avoid numeric character constants
What's special about the number 45
? If you care about its character value, (and if you're using an ASCII-like system), it's clearer to write '-'
instead.
Drop the commented-out code
I don't want to see using std::namespace;
, ever. Not even in a comment.
Improve the wrap-around logic
There's obvious inefficiency in:
head %= bufferSize;
%
uses division, but because we're incrementing, we know that head / bufferSize
is at most 1
. Instead, we can:
if (++head >= bufferSize)
head -= bufferSize;
On architectures such as ARM, there's no branch here, as any decent compiler will just use a condition flag on the subtraction.
Consider checking for overrun and underrun
At the moment, this buffer has no checking for the read and write positions crossing each other. Perhaps that's what you need, but it's quite unusual to have an unchecked circular buffer, and if you were holding (e.g.) audio data, you can imagine how it would sound, given the output you show.
Match new
and delete
Since you create buffer
using new
, you must use the array form delete
in the destructor:
CyclicBuffer::~CyclicBuffer()
{
delete buffer;
}
Style points
Prefer initializers to assignment
A constructor should initialize all the members of the class:
CyclicBuffer::CyclicBuffer(std::size_t buffSize)
: buffer(new char[bufferSize]),
bufferSize(buffSize),
head(0),
tail(0)
{
}
A good compiler will warn you if you have uninitialized members (e.g. g++ -Wall -Wextra
).
Note that I've also changed the argument to be a size_t
, as a negative size is meaningless. The int
members should all be size_t
, too.
Avoid numeric character constants
What's special about the number 45
? If you care about its character value, (and if you're using an ASCII-like system), it's clearer to write '-'
instead.
Drop the commented-out code
I don't want to see using std::namespace;
, ever. Not even in a comment.
edited Nov 12 at 14:11
answered May 25 '17 at 9:55
Toby Speight
23.2k538110
23.2k538110
Regarding the suggested fix about%
division, since it is alwaysincrementing
, we can putif (++head >= bufferSize) head = 0;
right? And it should be more efficient as we are avoiding the subtraction. Am I missing something here?
– Anubis
Jun 28 at 10:55
No, you're missing nothing - assigning zero is less work than subtraction, and would be a further improvement.
– Toby Speight
Jul 4 at 7:20
2
@TobySpeight : In fact,//
is the best prefix forusing namespace std;
– Calak
Nov 12 at 14:57
add a comment |
Regarding the suggested fix about%
division, since it is alwaysincrementing
, we can putif (++head >= bufferSize) head = 0;
right? And it should be more efficient as we are avoiding the subtraction. Am I missing something here?
– Anubis
Jun 28 at 10:55
No, you're missing nothing - assigning zero is less work than subtraction, and would be a further improvement.
– Toby Speight
Jul 4 at 7:20
2
@TobySpeight : In fact,//
is the best prefix forusing namespace std;
– Calak
Nov 12 at 14:57
Regarding the suggested fix about
%
division, since it is always incrementing
, we can put if (++head >= bufferSize) head = 0;
right? And it should be more efficient as we are avoiding the subtraction. Am I missing something here?– Anubis
Jun 28 at 10:55
Regarding the suggested fix about
%
division, since it is always incrementing
, we can put if (++head >= bufferSize) head = 0;
right? And it should be more efficient as we are avoiding the subtraction. Am I missing something here?– Anubis
Jun 28 at 10:55
No, you're missing nothing - assigning zero is less work than subtraction, and would be a further improvement.
– Toby Speight
Jul 4 at 7:20
No, you're missing nothing - assigning zero is less work than subtraction, and would be a further improvement.
– Toby Speight
Jul 4 at 7:20
2
2
@TobySpeight : In fact,
//
is the best prefix for using namespace std;
– Calak
Nov 12 at 14:57
@TobySpeight : In fact,
//
is the best prefix for using namespace std;
– Calak
Nov 12 at 14:57
add a comment |
up vote
0
down vote
Constructor initialization - 0 out the array
buffer(new T[size]{})
will set all the values to 0 instead of random garbage
template<class T>
CyclicBuffer<T>::CyclicBuffer(std::size_t size): head(0), tail(0), buffer(new T[size]{}), size(size) {
}
Adding and Getting can use equality comparison
Comparing equality rather than subtracting/greater or equals makes it more readable
template<class T>
void CyclicBuffer<T>::add(T val) {
if (head == size)
head = 0;
buffer[head++] = val;
}
template<class T>
T CyclicBuffer<T>::get() {
if (tail == size)
tail = 0;
return buffer[tail++];
}
Use template to make your structure generic
You will be able to insert any type:
CyclicBuffer<char> charCyclicBuffer(5);
CyclicBuffer<int> intCyclicBuffer(5);
Use std::size_t for portability
Besides portability - size, head, tail will never be negative
template<class T>
class CyclicBuffer{
public:
explicit CyclicBuffer(std::size_t size);
~CyclicBuffer();
void add(T val);
T get();
inline std::size_t getSize() {
return this->size;
}
private:
T *buffer;
std::size_t size;
std::size_t head;
std::size_t tail;
};
New contributor
add a comment |
up vote
0
down vote
Constructor initialization - 0 out the array
buffer(new T[size]{})
will set all the values to 0 instead of random garbage
template<class T>
CyclicBuffer<T>::CyclicBuffer(std::size_t size): head(0), tail(0), buffer(new T[size]{}), size(size) {
}
Adding and Getting can use equality comparison
Comparing equality rather than subtracting/greater or equals makes it more readable
template<class T>
void CyclicBuffer<T>::add(T val) {
if (head == size)
head = 0;
buffer[head++] = val;
}
template<class T>
T CyclicBuffer<T>::get() {
if (tail == size)
tail = 0;
return buffer[tail++];
}
Use template to make your structure generic
You will be able to insert any type:
CyclicBuffer<char> charCyclicBuffer(5);
CyclicBuffer<int> intCyclicBuffer(5);
Use std::size_t for portability
Besides portability - size, head, tail will never be negative
template<class T>
class CyclicBuffer{
public:
explicit CyclicBuffer(std::size_t size);
~CyclicBuffer();
void add(T val);
T get();
inline std::size_t getSize() {
return this->size;
}
private:
T *buffer;
std::size_t size;
std::size_t head;
std::size_t tail;
};
New contributor
add a comment |
up vote
0
down vote
up vote
0
down vote
Constructor initialization - 0 out the array
buffer(new T[size]{})
will set all the values to 0 instead of random garbage
template<class T>
CyclicBuffer<T>::CyclicBuffer(std::size_t size): head(0), tail(0), buffer(new T[size]{}), size(size) {
}
Adding and Getting can use equality comparison
Comparing equality rather than subtracting/greater or equals makes it more readable
template<class T>
void CyclicBuffer<T>::add(T val) {
if (head == size)
head = 0;
buffer[head++] = val;
}
template<class T>
T CyclicBuffer<T>::get() {
if (tail == size)
tail = 0;
return buffer[tail++];
}
Use template to make your structure generic
You will be able to insert any type:
CyclicBuffer<char> charCyclicBuffer(5);
CyclicBuffer<int> intCyclicBuffer(5);
Use std::size_t for portability
Besides portability - size, head, tail will never be negative
template<class T>
class CyclicBuffer{
public:
explicit CyclicBuffer(std::size_t size);
~CyclicBuffer();
void add(T val);
T get();
inline std::size_t getSize() {
return this->size;
}
private:
T *buffer;
std::size_t size;
std::size_t head;
std::size_t tail;
};
New contributor
Constructor initialization - 0 out the array
buffer(new T[size]{})
will set all the values to 0 instead of random garbage
template<class T>
CyclicBuffer<T>::CyclicBuffer(std::size_t size): head(0), tail(0), buffer(new T[size]{}), size(size) {
}
Adding and Getting can use equality comparison
Comparing equality rather than subtracting/greater or equals makes it more readable
template<class T>
void CyclicBuffer<T>::add(T val) {
if (head == size)
head = 0;
buffer[head++] = val;
}
template<class T>
T CyclicBuffer<T>::get() {
if (tail == size)
tail = 0;
return buffer[tail++];
}
Use template to make your structure generic
You will be able to insert any type:
CyclicBuffer<char> charCyclicBuffer(5);
CyclicBuffer<int> intCyclicBuffer(5);
Use std::size_t for portability
Besides portability - size, head, tail will never be negative
template<class T>
class CyclicBuffer{
public:
explicit CyclicBuffer(std::size_t size);
~CyclicBuffer();
void add(T val);
T get();
inline std::size_t getSize() {
return this->size;
}
private:
T *buffer;
std::size_t size;
std::size_t head;
std::size_t tail;
};
New contributor
New contributor
answered 1 hour ago
Eugene Barnett
1
1
New contributor
New contributor
add a comment |
add a comment |
Thanks for contributing an answer to Code Review Stack Exchange!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
Use MathJax to format equations. MathJax reference.
To learn more, see our tips on writing great answers.
Some of your past answers have not been well-received, and you're in danger of being blocked from answering.
Please pay close attention to the following guidance:
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f164130%2felegant-circular-buffer%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
2
I have usually found an additional value to be necessary: the number of items in the buffer, because when the index
head == tail
you cannot know whether a circular buffer is empty or full.– Weather Vane
May 24 '17 at 19:44
1
... and sweeter, because you don't need to be continually making a
(head - tail + size) % size
calculation to determine how many items there are in the buffer.– Weather Vane
May 24 '17 at 19:53
2
The most heavy operation in your actual code is taking the remainder. Its performance varies target-wise, and you might find some performance gain by switching it to comparison with the container size. Also, restricting the possible buffer sizes to the power of two will allow you to substitute the division with bitwise and.
– iehrlich
May 24 '17 at 19:53
2
Also, you don't need both include guards and
#pragma once
.– iehrlich
May 24 '17 at 19:54
2
Don't forget to change your
delete
todelete
– Ceros
May 24 '17 at 21:25