c++ - 为 C++ ostream 自定义流缓冲区

标签 c++ iostream streambuf

我正在为输出流实现我自己的流缓冲区。基本上它是一个类似 vector 的流缓冲区,每次溢出函数都会简单地将缓冲区重新分配到两倍大。 sync 函数会将所有数据写出到由文件描述符 fd 指定的设备。

class MyStreamBuf : public ::std::streambuf {

  constexpr static size_t INIT_BUFFER_SIZE {1024};

  public: 

    MyStreamBuf();
    ~MyStreamBuf();

    void fd(const int);

    int sync() override;
    int_type overflow(int_type ch = traits_type::eof()) override;

  private:

    int _fd {-1};
    size_t _size;
    char_type* _base;    
    void _resize(const size_t);
};


MyStreamBuf::MyStreamBuf() {
  _size = INIT_BUFFER_SIZE;
  _base = static_cast<char_type*>(malloc(_size * sizeof(char_type)));
  setp(_base, _base + _size - 1);   // -1 to make overflow easier.
}

// Destructor.
MyStreamBuf::~MyStreamBuf() {
  ::free(_base);
}

// Procedure: fd
// Change the underlying device.
void MyStreamBuf::fd(const int fd) {
  _fd = fd;
}

// Procedure: _resize
// Resize the underlying buffer to fit at least "tgt_size" items of type char_type.
void MyStreamBuf::_resize(const size_t tgt_size) {

  // Nothing has to be done if the capacity can accommodate the file descriptor.
  if(_size >= tgt_size) return;

  // Adjust the cap to the next highest power of 2 larger than num_fds
  for(_size = (_size ? _size : 1); _size < tgt_size; _size *= 2);

  // Adjust and reset the memory chunk.
  _base = static_cast<char_type*>(::realloc(_base, _size*sizeof(char_type)));

  setp(_base, _base + _size - 1);   // -1 to make overflow easier.
}

int MyStreamBuf::sync() {

  int res = 0;

  ::std::ptrdiff_t remain = pptr() - pbase();

  while(remain) {

    issue_write:
    auto ret = ::write(_fd, pptr() - remain, remain);

    if(ret == -1) {
      if(errno == EINTR) {
        goto issue_write;
      }
      else if(errno == EAGAIN) {
        break;
      }
      else {
        res = -1;
        break;
      }
    }
    remain -= ret;
  }

  if(remain) {
    ::memcpy(pbase(), pptr() - remain, remain*sizeof(char_type));
  }
  pbump(pbase() + remain - pptr());

  return res;
}

typename MyStreamBuf::int_type MyStreamBuf::overflow(int_type ch) {
  assert(traits_type::eq_int_type(ch, traits_type::eof()) == false);
  _resize(_size * 2);
  return ch;
}

但是,在用我自己的缓冲区替换 cout 时出现段错误。在与 GDB 斗争之后,我找不到错误在哪里。

// Function: main
int main() {

  auto fd = open("./test.txt",  O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR);

  MyStreamBuf d;

  d.fd(fd);

  ::std::cout.rdbuf(&d);

  ::std::cout << 1 << " " << 2 << ::std::endl;

  close(fd);

  return 0;
}

这个实现有什么问题吗?我看到许多文章通常会覆盖 syncoverflow 是必需的。

最佳答案

问题似乎是您的对象 dstd::cout 之前被销毁,因此最终调用销毁全局对象,包括刷新缓冲区,并且在 main() 结束后发生(记住它是一个全局对象),尝试对不再存在的 streambuf 对象执行操作。您的缓冲区对象绝对应该比您关联的流长。

在你的程序中使用它的一种方法是将 d 变成一个你永远不会删除的指针。或者,您可以在使用时保留本地对象,但调用 std::cout.flush(),然后将 cout 的缓冲区分配给其他对象(甚至nullptr) 超出范围之前。

在测试您的程序时(在我发现问题之前),我做了一些对我有意义的小改动。例如,在成功写入描述符后,您可以简单地bump(ret)(您已经知道ret!=-1,因此可以安全使用)。

我没有做但你可以考虑的其他更改是让构造函数本身设置描述符,让析构函数关闭悬空描述符,并且可能更改面向 C 的动态分配 malloc ()/realloc()/free() 到面向 C++ 的 std::vector

说到分配,您在使用 realloc() 时犯了一个很常见的错误。如果重新分配失败,realloc() 将保持原始指针不变,并通过返回空指针来指示失败。由于您使用相同的指针来获取返回值,因此您可能会丢失对仍分配的内存的引用。所以,如果您根本不能使用 C++ 容器而不是 C 指针,您应该将代码更改为更像这样的内容:

char *newptr;
newptr=static_cast<char *>(realloc(ptr, newsize));
if(newptr)
    ptr=newptr;
else {
    // Any treatment you want.  I wrote some fatal failure code, but
    // you might even prefer to go on with current buffer.
    perror("ralloc()");
    exit(1);
}

关于c++ - 为 C++ ostream 自定义流缓冲区,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/36900226/

相关文章:

c++ - 成员模板的别名模板

c++ - 使用 initializer_list 的模糊重载解析

c++ - C++ istream 中的行号?

c++ - 使用 getline(cin,n) 输入;没有打印第一个输入,我也没有使用 cin>> 在任何地方输入

c++ - boost.asio - 设置最大读取流大小

C++ 实现(XOR、IMPLIES、IFF)

c++ - 在 C++ 中导入/包含所有库的快捷方式

.net - 如何将System.IO.Stream转换为字符串,然后再返回到System.IO.Stream

c++ - std::stringstream 缓冲区操作

c++ - 线程安全日志记录