如果使用("some content", std::ios::in|std::ios::ate)构造std::stringstream,tellg()的预期行为是什么?

7
我有一段代码,让我感到惊讶(使用libstdc++4.8)...
#include <iostream>
#include <sstream>
#include <string>

using namespace std;

int main() {
    std::string s("some content");
    std::stringstream ss(s, std::ios::in|std::ios::ate);
    std::istream& file = ss;
    //ss.clear(); Makes no difference...
    std::cout << "tellg() pos: " << file.tellg() << std::endl;
    return 0;
}

...其输出如下。

tellg()位置:0

这种行为与使用std::ifstream(std::ios::ate)时不同。

  • 这种行为是否正确/预期?
  • 尽管使用ate打开,是否需要显式地调用seekg(0, std::ios::end)?
  • 清除状态不会有任何影响。
  • 请注意字符串有内容
2个回答

2

这与标准规定完全一致。以下是相关细节:

您使用的构造函数版本:

通过调用std::basic_streambuf的默认构造函数,构造一个std::basic_stringbuf对象,将字符序列初始化为空字符串,并将模式设置为which,然后通过调用str(new_str)来初始化关联的字符序列。

这里不需要关注basic_stringbuf的默认构造函数,然后是std::basic_stringbuf::str

删除此std::basic_stringbuf的整个底层字符序列,然后配置一个包含s内容副本的新底层字符序列。对于追加流(模式&ios_base::ate == true),pptr() == pbase() + s.size(),因此随后的输出将附加到从s中复制的最后一个字符。

最后是tellg(),它在缓冲区上调用pubseekoff

如果which包括ios_base::in并且此缓冲区已打开进行读取(即如果((which&ios_base::in) == ios_base::in),则重新定位读取指针std::basic_streambuf::gptr:...那么newoff是指针的当前位置(在这种情况下为gptr()-eback())。

总之:由于您没有以任何方式修改获取位置(构造函数只修改了放置位置),因此它返回0。


这是一个有趣的决定。其影响在于,对于依赖于工厂返回iostream的测试来说,在仅输入模式(ios::in)下创建fstream(mode)和stringstream(mode)对象的行为是不同的。我在测试中使用了std::stringstream来模拟“内存中”文件,并且代码大多依赖于istream、ostream或iostream。行为上的差异使我的测试意外失败。 - Werner Erasmus
此外,对我来说,这里稍微缺少“是一个”关系。可以承认,文件流不是字符串流,但我觉得使ios::in | ios::ate的行为更加“异构”会有价值。 - Werner Erasmus
1
确实如此。文件流不是字符串流,它们之间没有“is-a”关系。因此,我在标准中没有看到立即的缺陷。然而,我承认,当调用str()时,我不明白为什么获取指针不能更新字符串缓冲区。您可能需要提交一个更改提案。 - SergeyA

2

简而言之:

tellg() 返回 gptr()-eback(),在 stringstream(因此也是 basic_stringbuf)构造函数中提供 ios_base::in 标志后,后置条件为 gptr() == eback()

因此,期望 / 强制为 0

详细说明:

  • tellg() 返回 rdbuf()->pubseekoff(0, std::ios_base::cur, std::ios_base::in)
  • rdbuf() 返回 const_cast<basic_stringbuf*>(&sb)
  • pubseekoff(0, std::ios_base::cur, std::ios_base::in) 调用 seekoff(0, std::ios_base::cur, std::ios_base::in)
  • seekoff 返回 gptr()-eback()
  • eback() 返回获取区域的开头指针
  • gptr() 返回当前获取点

stringstream 初始化:

basic_stringstream (basic_string const &str, ios_base::openmode which);

作用:构造一个basic_stringstream类的对象,将基类初始化为basic_iostream(&sb),并使用basic_stringbuf(str, which)初始化sb。

 

basic_stringbuf(basic_string const &str, ios_base::openmode which)

作用:构造一个basic_stringbuf类的对象,将基类初始化为basic_streambuf(),并使用which初始化mode。然后调用str(s)

 

void basic_stringbuf::str(const basic_string<charT,traits,Allocator>& s);

作用:将s的内容复制到basic_stringbuf底层的字符序列中,并根据mode初始化输入和输出序列。

后置条件:

  • 如果mode & ios_base::outtrue,则pbase()指向第一个底层字符,且epptr() >= pbase() + s.size()成立;

  • 如果mode & ios_base::atetrue,则pptr() == pbase() + s.size()成立,否则pptr() == pbase()成立;

  • 如果mode & ios_base::intrue,则eback()指向第一个底层字符,且gptr() == eback()egptr() == eback() + s.size()都成立。

最后一部分与本文相关:如果提供了ios_base::in,则后置条件gptr() == eback()成立,由于tellg()返回gptr()-eback(),因此结果需要为零。


如果 mode & ios_base::in 为真,则 eback() 指向第一个底层字符,这并不反映直觉行为,在我看来。 - Werner Erasmus
1
@WernerErasmus:一个文件只有一个“位置指针”,而stringstream有两个。一个用于读取,一个用于写入。在提供ate时,我没有看到同时拥有它们的直接好处。在我看来,实际上可以使用ate将字符串附加到字符串流中,同时仍然能够从字符串流中从头开始读取,这是一件好事。唯一让我感到有些反直觉的是,当使用operator<<时,初始化为strinstream{str}(不带ate)的字符串不会附加到str,但另一方面,如果默认情况下没有atb来反转该行为。 - Pixelchemist

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接