例解流缓冲的使用

众所周知,C++标准库提供的iostream提供3种形式的缓冲:

§           不带缓冲区,这样与fwrite等操作一致(理论上就不是iostream了);

§           空间自动管理的缓冲区,下一个可写位置总是为末边界+1

§           外置的缓冲区,如定义局部字符串数组,并将此区域传递给iostream

在带缓冲区的情况下,iostream本身却不具体负责其相关功能,而把此任务交给了basic_streambuf及其派生类来负责,常见的如basic_filebuf(用于文件操作的缓冲区)和basic_stringbuf(用于字符串操作的缓冲区)。如上所述,iostreamstreambuf之间的关系如图所示(iostreambasic_iostream的具体实例,如cout, cin):

basic_iostream的两大分支basic_istreambasic_ostream都是basic_streambuf的友元类,而basic_iostream包含了一个basic_streambuf的派生类的实例,该实例可以通过basic_iostream的成员函数rdbuf()来获取,并通过构造函数传入;我们经常使用的流操作符<<实际上是通过调用basic_streambuf的相关操作实现的。例如输出流的<<操作,其调用过程如图所示:

1)调用输出流的_M_put_char(不同实现的名字不同,此为STLport的版本)

2)_M_put_char函数通过记录的basid_streambuf实例,调用其sputc的方法;

3) basid_streambufsputc函数会把字符作为参数调用overflow操作;

4)overflow中会调用无换成的系统函数fwrite来写到文件。

Overflowbasic_streambuf类的一个保护虚成员函数,在写指针达到缓冲区末尾是会激活一次(即溢出),其基本功能是:

1)如果写指针可写,则把该字符放到缓冲区,并改变写指针;

2)如果缓冲区是自动维护的,则为该字符分配新的空间,并存储到缓冲区中去;

3)处理本地化相关工作(不是关注的重点)

基类basic_streambufoverflow函数只是简单的返回traits_type::eof()即可,具体功能交给了派生类;对于basic_filebuf而言,overflow最重要的就是调用fwrite,把字符写到文件中。

同时必须注意到VC6版本的overflow调用fwrite时,一次只写入一个字符。

综上所述,我们要控制标准输入输出流的缓冲区行为,我们必须生成自己的basic_streambuf派生类,并生成一个该类的对象,传递给标准准输入输出流;由于基类basic_streambuf很多功能没有具体实现,为了方便,我们需要从basic_filebuf或者basic_stringbuf派生。

这里举的例子是为了实现超过指定上限后自动覆盖缓冲区内容:如最大值为6时, 789将覆盖123,对于默认basic_filebuf则会不断增长以满足需求;这种缓冲区对于日志操作很有帮助。

   

  具体处理步骤为:

1)      basic_filebuf派生自己的流缓冲类:SimpleBuffer

template <class charT, class traits = std::char_traits<charT> >

class SimpleBuffer: public std::basic_filebuf<charT, traits>

{

public:

       //方便模板内部使用的类型定义

    typedef charT char_type;     

    typedef traits traits_type;

    typedef typename traits_type::int_type int_type;

    typedef typename traits_type::off_type off_type;

    typedef typename traits_type::pos_type pos_type;

 

       //必须提供最大长度的构造函数

    SimpleBuffer(std::streamsize sz)                         

        : max_size (sz), cur_size (0) { }

private:

       std::streamsize max_size;

    std::streamsize cur_size;

};

私有成员max_sizecur_size分别用于记录最大字符数和当前的字符数。

2)      重载overflow函数,改变其溢出控制逻辑

//重载basic_streambuf::overflow实现对缓冲区的控制

    virtual int_type overflow(int_type c = traits::eof()) 

       {

              std::streamsize len = pptr () - pbase ();   

              std::streamsize rem = cur_size + len - max_size;        

 

              std::basic_string<charT, traits> saved;

 

              //如果当前字符串长度超出了最大剩余空间      

              if (rem > 0)

              {                                    

                     //计算剩余超出部分

                     //把真正先后面移动,实际上是一个输出缓冲区的大小

                     this->pbump(-rem);

 

                     //记录超出部分

                     saved.assign (pptr (), rem);

                     if (!traits_type::eq_int_type (c, traits_type::eof ()))

                     {

                            saved += c;

                     }

                     len = max_size - cur_size;

                     //由于已经记录了当前溢出的字符,所以调用基类的overflow时不需要把溢出字符传入

                     //则把溢出字符换成eof()

                     c = traits_type::eof ();

              }

 

              //调用基类的overflow,实现对输出缓冲区的写操作

              const int_type ret = basic_filebuf<charT, traits>::overflow(c);

              //cur_size += len;                                         

              cur_size ++;

 

              if (cur_size == max_size)

              {                             

                     cur_size = max_size + 1;

                     //既然超出了最大长度,则把指针移到首位

                     pubseekoff(0, std::ios_base::beg);

                     //复位计数器

                     cur_size = 0;

              }

 

              if (saved.size ())

              {

                     //把保存的内容再放回到输出缓冲区中

                     xsputn(saved.data (), saved.size ());

              }

 

              return ret;

 

       }

对于缓冲区自动管理的类型(默认情况),由于pptrpbase为空,cur_size不能通过len来改变,而是默认cur_size ++处理;同时这种情况下,rem永远不会大于0,则上述代码中,控制逻辑主要体现在cur_size==max_size后的情况,即达到上限的情况,这时需要把写指针放到缓冲区的首位,调用basic_streambufpubseekoff函数实现,并把计数器复位。

3)      给出自己的输出流类,该类包含SimpleBuffer的缓冲区对象

template <class charT, class traits = std::char_traits<charT> >

class SimpleStream: public std::basic_iostream<charT, traits>

{

    SimpleBuffer<charT, traits> buf;                       

 

public:

    typedef charT char_type;   

    typedef traits traits_type;

    typedef typename traits_type::int_type int_type;

    typedef typename traits_type::off_type off_type;

    typedef typename traits_type::pos_type pos_type;

 

    SimpleStream(std::streamsize sz, const char* file)

           : std::basic_iostream<charT, traits>(&buf), buf (sz)

       {           

              this->init(&buf);                               

              if (!buf.open (file, std::ios_base::out))        

              {

            this->setstate (std::ios_base::failbit);

              }

       };  

    SimpleBuffer<charT, traits> *rdbuf () const

       {          

        return (SimpleBuffer<charT, traits>*)&buf;

    }

};

在指定文件名的构造函数中,完成了输出流的初始化,并打开文件。

这样就能实现刚才设计的功能了。

 

最后要说一下外置缓冲区的情况,如

char buf [33];                                

test.rdbuf ()->pubsetbuf (buf, sizeof buf); 

通过basic_filebuf的成员函数pubsetbuf,理论上就完成了缓冲区设置,但是在VC6下,pptrpbase取到的指针都是空的;要解决这个问题,可以通过定义setppublic方法来完成:

void setp(charT* pbeg, charT* pend)

       {

              basic_filebuf<charT, traits>::setp(pbeg, pend);

       }

它实际上是调用basic_filebuf的保护成员setp实现的,把缓冲区的首和末指针赋值,并设定缓冲区不自动增长,这时候overflow函数会每写sizeof(buf)个字符激活一次,这样remif语句将起作用,并需要将cur_size的计算改为cur_size+= len了。

上述代码中,往文件中写的功能都是通过调用basic_filebufoverflow来实现的;如果改成外置缓冲后,除最后一个溢出字符外,在VC6情况下,其它字符basic_filebuf不负责往文件中写(fwrite),所以需要特别处理;而STLport中的overflow实现,包含了写整个缓冲区内容,不必单独处理。

小结:编写自己流缓冲区情况一般适用于需要进行字符转换(如把小写转换为大写等等)和缓冲区大小的控制(对缓冲区空间严格要求),可以说是特殊场合使用;一般不需要进行如此操作。写自己的流缓冲区时,需要知道哪些函数需要重载(经常是overflow);应该从那个类派生(如basic_filebuf)。

注:例子在 VC6 下编译调试通过。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值