设计一个循环缓冲区模板
开发中经常用到循环缓冲区作为数据FIFO使用,通常设计一个结构体数组作为缓冲区,结构体单元就是一个缓冲数据单元,使用上遵守先进先出原则,在缓冲区中形成一个数据队列,定义两个位置指针,分别指向队尾和队首,需要通过两个指针的位置判断缓冲区满、空和队列状态,通过位置指针取出和存入数据,同时还要控制两个位置指针不要越界,能够到达数组结尾后返回开头,实现循环缓冲功能。
函数(类)方法:
一般设计FIFO的做法是一事一议,因为不同FIFO应用的数据格式各不相同,然而项目中可能需要不止一种数据FIFO,为每种FIFO维护一套访问函数显然不科学,FIFO的操作方法是相同的,不同的只是数据格式,进一步的做法是设计一组操作函数(或一个类),将FIFO位置指针初始化、数据存入和取出都归纳为操作函数,即将FIFO操作方法统一起来,这么做会漂亮很多,下面是我设计的操作函数定义:
//环形缓冲区溢出行为
typedef enum{
OvDrop = 0,//丢弃
OvCover, //覆盖
}OverflowBehavior;
//环形缓冲区访问句柄
typedef struct
{
unsigned long begin;//填充起始,如果与end重叠,表示溢出或空
unsigned long end;//指向末尾空位置
OverflowBehavior behavior;//溢出行为
bool overflow;//溢出标志
unsigned long size;
}Handle;
void Init(Handle &hrb, OverflowBehavior b, unsigned long size);
//返回-1表示溢出,返回下一个空位置,填充缓冲区还是要填充hrb.end位,填充后将返回值写入hrb.end
int PushReq(Handle &hrb);
void PushConfirm(Handle &hrb, unsigned long newEnd);
//返回-1表示空,返回下一个起始位置,读取缓冲区还是要读hrb.begin位,完成后将返回值写入hrb.begin
int PopReq(Handle &hrb);
void PopConfirm(Handle &hrb, unsigned long newBegin);
这一组函数用类包装一下更简洁,其实现了对FIFO的管理,提供了存取函数和操作确认函数(为什么要有操作确认呢?因为有些FIFO应用,存取过程需要多步操作才能完成),但这种方法并没有将FIFO本身纳入,FIFO的创建、初始化和删除都需要调用方自行完成,并且要注意时机,以免与操作函数冲突。
模板方法:
为了实现上述目标,设计一个完整的FIFO机制,我们使用类模板,直接上代码:
template <typename T>
class CRingBuffer
{
public:
CRingBuffer(unsigned long size, OverflowBehavior behavior)
{
m_begin = 0;
m_end = 0;
m_behavior = behavior;
m_overflow = false;
m_size = size;
m_object = new T[m_size];
}
~CRingBuffer()
{
delete T[];
}
private:
unsigned long m_begin;
unsigned long m_end;
OverflowBehavior m_behavior;
bool m_overflow;
unsigned long m_size;
T* m_object;
public:
T* PushRequest(){
//缓冲区尾巴追上头了或者缓冲区空
if(m_begin == m_end)
{
//追上溢出了
if(m_overflow)
{
switch(m_behavior)
{
//覆盖
case OvCover:
//起始被覆盖,指针向下指
if(m_begin == m_size-1)
m_begin = 0;
else
m_begin++;
break;
//丢弃
case OvDrop:
default:
return NULL;
}
}
}
return &m_object[m_end];
}
void PushConfirm()
{
//到达缓冲区自然末端,回到起始
if(m_end == (m_size-1))
m_end = 0;
else
m_end++;
//溢出
if(m_end == m_begin)
m_overflow = true;
}
T* PopRequest()
{
//判断空
if ((!m_overflow) && (m_begin == m_end))
return NULL;
return &m_object[m_begin];
}
void PopConfirm()
{
if (m_begin == (m_size - 1))
m_begin = 0;
else
m_begin++;
m_overflow = false;
}
};
使用:
typedef struct{
unsigned long id;
string info;
}RingBufferInfo;
#define MAX_INFO_NUM 4096
int main(int argc, char *argv[]){
unsigned long test_id = 0x55aa;
stringtest_info = "test";
CRingBuffer< RingBufferInfo > infoBuf(MAX_INFO_NUM,OvCover);
RingBufferInfo *pInfo = infoBuf.PushRequest();
If(pInfo)
{
pInfo->id = test_id;
pInfo->info = test_info;
infoBuf.PushConfirm();
}
test_id = 0;
test_info = "";
pInfo = infoBuf.PopRequest();
If(pInfo)
{
test_id = pInfo->id;
test_info = pInfo->info;
infoBuf.PopConfirm();
}
printf("id: %d ,string:%s\n", test_id, test_info);
}
总结:
<queue>同样的实现。
模板可以对数据的操作进行抽象,对不同的数据结构使用统一的操作方法,这也是一种面向对象,不同于类派生,类的重点在分类,模板的重点在方法的套用。虽然上述功能用类也能实现,但每设计一个FIFO都要建立一个包含缓冲区的类再从包含操作方法的基类派生真的好么?就如同人有人流感,禽有禽流感,得了禽流感的人,用类描述好还是用模板呢?