标准是不是要求流构造函数不访问流缓冲区?

Posted

技术标签:

【中文标题】标准是不是要求流构造函数不访问流缓冲区?【英文标题】:Does standard mandates that stream-constructors don't access stream buffer?标准是否要求流构造函数不访问流缓冲区? 【发布时间】:2021-01-24 08:13:47 【问题描述】:

This(相当老的)关于 iostreams 和 streambuf 的文章认为,以下代码是可以的:

class DerivedStreamBuf : public std::streambuf 
  // ...
;

class DerivedOutputStream : public std::ostream 
  public:
    DerivedOutputStream():
      std::ios(0), std::ostream(&dsb)         //1
    // ...
  private:
    DerivedStreamBuf dsb;
    // ...
;

这可能是有问题的,因为当ostream 被构造时,dsb 尚未初始化,因此 UB 可能是效果。对于析构函数,它可能是相反的:dsb 已经被析构并且可以在ostream 的析构函数中使用。

但是,文章认为,“C++ 标准要求没有父类构造函数或析构函数(ios、istream 或 ostream)访问流缓冲区”。

虽然析构函数的情况很简单,例如对于~ostream

虚拟~basic_ostream(); 备注:不对rdbuf()进行任何操作。

constructor 不太清楚:

显式 basic_ostream(basic_streambuf* sb);效果: 用 basic_ios​::​init(sb) ([basic.ios.cons])。

这是否意味着这是唯一可能的效果,这个标准公式是否不允许其他效果,它们是 std::ostream 的实现细节并且可能访问未初始化的 dsb

我的问题:该标准是否保证上述代码是可移植的,即适用于 std::ostream 的所有符合标准的实现?

【问题讨论】:

【参考方案1】:

该标准是否保证上述代码是可移植的,即适用于所有符合标准的std::ostream 实现?

是的。

在调用std::ostream(&dsb) 时,DerivedOutputStreamdsb 成员已分配,但未初始化。意思是,我们可以通过我们可能还没有读取它的(未初始化的)值来获取它的地址。

正如问题中所链接的,[ostream.cons] 指定了 std::basic_ostream 的构造函数,特别是在您的示例中选择的构造函数是 [ostream.cons]/1

显式 basic_ostream(basic_streambuf* sb);

效果:basic_­ios<charT, traits>​::​init(sb)([basic.ios.cons])初始化基类子对象。

后置条件: rdbuf() == sb.

[tab:basic.ios.cons] 描述了调用void init(basic_streambuf<charT, traits>* sb) 的效果,特别是上面构造函数中描述的后置条件:

rdbuf() == sb

其余部分不执行对sb 指向的底层缓冲区的读取访问。

现在,rdbuf() 只是一个指针,所以这意味着传递给 init 的 sb 指针按值(即指针)将用于初始化 rdbuf()。但是,此时读取sb 指向的底层缓冲区没有副作用。

[...] 不允许其他效果,...?

确实,特别是void init(basic_streambuf<charT, traits>*) 函数的实现需要尊重而不是扩展[tab:basic.ios.cons] 的明确规定的效果。


这篇(相当古老的)文章...

请注意,[ostream.cons] 的状态自 C++ 时代以来基本没有变化;我们可以例如从the September 1994 C++ Working Paper看[lib.input.output]

27.2.4.1.1 basic_ostream 构造函数 [lib.basic.ostream.sb.cons]

basic_ostream(basic_streambuf<charT,baggage>* sb);

1 构造一个basic_ostream类的对象,通过调用basic_ios&lt;charT,baggage&gt;::init(sb)为基类分配初始值。

27.1.3.1.34 basic_ios::init [lib.basic.ios::init]

void init(basic_streambuf<charT,baggage>* sb_arg);

1 该函数的后置条件如表8所示:

                      Table 8--init effects

               +----------------------------------+
               |Element            Value          |
               +----------------------------------+
               |sb        sb_arg                  |
               |tiestr    a null pointer          |
               |state     goodbit  if  sb_arg  is |
               |          not   a  null  pointer, |
               |          otherwise badbit.       |
               |except    goodbit                 |
               |fmtfl     skipws | dec            |
               |wide      zero                    |
               |prec      6                       |
               |fillch    the space character     |
               |loc       new   locale(),   which |
               |          means the default value |
               |          is the  current  global |
               |          locale;9)               |
               |iarray    a null pointer          |
               |parray    a null pointer          |
               +----------------------------------+

通过具有明确定义的效果的init() 调用来描述相同的委托。

【讨论】:

您回答的关键点是,标准中的“效果”意味着只允许这些效果。你能提供一个来源吗?例如。调用父构造函数也可能是一种效果。没有调用父构造函数吗? @ead 我对库规范的解释一直是效果准确地描述了给定函数的动作,并且明确说明了任何先决条件(例如,输入迭代器或指针是否需要可解引用)。请参阅 general structure description 以及 alg.swap 的规范,其中明确要求输入迭代器作为函数的先决条件可取消引用(将出现更少的 UB)。 我会假设,将指针传递给有效对象是隐含的要求。例如,ifstream(const char* s, ...); 没有明确指定 s 必须是指向有效 c 字符串 (eel.is/c++draft/ifstream#cons-2) 的指针,但是传递指向某些(未初始化的)内存的指针是 UB。 另外,“效果”当然不能描述所有可能的效果。例如,在某些(大多数?)实现中,basic_ostream 的构造函数也会初始化成员变量,其中gcount 的结果存储为 0,但这是一个实现细节,标准中的“效果”下未提及.所以我不确定是否可以遵循,该标准提供了任何保证。

以上是关于标准是不是要求流构造函数不访问流缓冲区?的主要内容,如果未能解决你的问题,请参考以下文章

如何将 boost::iarchive 的流设置为空流

缓冲流如何工作?

字节缓冲流构造方法演示示例

文件流之字节缓冲流(BufferedInputStream BufferedOutputStream)

FileStream 构造函数中有一个参数是buffersize,请问这个值设置的大小,有啥意义。谢谢了。

我可以使用在类构造函数中初始化的流类型的成员变量吗?