Boost iostreams input_seekable source的seek函数与tellg()行为疑问
问题描述
我正尝试用Boost 1.75.0的iostreams库实现一个input_seekable类型的source,数据源基于固定长度的char数组。目前读取功能正常,但seek函数存在问题:按照官方文档里的容器设备示例实现seek后,我通过streambuffer构造std::istream,调用seekg(0, std::ios_base::end)后用tellg()获取流大小。我认为seekg(0, std::ios_base::end)应该把读取指针放在缓冲区最后一个元素的之后,此时tellg()应该返回流的总字节数,但文档示例的实现让指针指向最后一个元素,导致tellg()返回错误数值。想请教tellg()的预期行为究竟是什么?
回答
tellg()的标准预期行为
std::istream::tellg()返回的是当前读取指针相对于流起始位置的字节偏移量,这个偏移量代表下一次读取操作的起始位置。当调用seekg(0, std::ios_base::end)时,C++标准明确要求指针被定位到流的末尾之后——比如一个包含N个字节的数据源,此时偏移量为N,tellg()的返回值就是N,也就是流的总字节数。
Boost iostreams input_seekable的seek实现要求
Boost iostreams的input_seekable设备必须遵循标准输入流的语义,其seek函数的行为必须匹配std::istream的规则:
- 基于char数组的input source,有效读取区间是左闭右开的
[base_ptr, base_ptr + size) seek(0, std::ios_base::end)必须将内部指针设置为base_ptr + size,而非base_ptr + size - 1- 合法的偏移量范围是
[0, size](包含size,因为允许定位到末尾之后)
文档中的容器设备示例可能是针对双向读写设备设计的,这类场景下seek到end可能允许指向最后一个可写入/读取的元素,但不适用于纯input_seekable的source,这就是你遇到问题的原因。
正确的seek函数实现示例
以下是符合输入流语义的seek实现逻辑:
std::streampos seek(boost::iostreams::stream_offset off, std::ios_base::seekdir way) { // 当前指针相对于起始位置的偏移量 std::streampos current_pos = current_ptr_ - base_ptr_; std::streampos target_pos; switch(way) { case std::ios_base::beg: target_pos = off; break; case std::ios_base::cur: target_pos = current_pos + off; break; case std::ios_base::end: // 从末尾偏移:size_是数组总字节数,off为0时定位到末尾之后 target_pos = size_ + off; break; default: throw std::ios_base::failure("invalid seek direction"); } // 约束目标偏移量在合法范围内 target_pos = std::max(std::streampos(0), std::min(target_pos, std::streampos(size_))); current_ptr_ = base_ptr_ + target_pos; return target_pos; }
按照这个逻辑实现后,seekg(0, std::ios_base::end)会将指针定位到数组末尾之后,tellg()将返回正确的总字节数size_。
内容的提问来源于stack exchange,提问作者Gustavo

