标准是否规定流构造函数不得访问流缓冲区?

5

这篇(相当老的)关于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()执行任何操作。
对于构造函数来说,则不太清楚:
显式基本_ostream(basic_streambuf* sb);效果: 使用basic.ios::init(sb)([basic.ios.cons])初始化基类子对象。
这是否意味着这是唯一可能的影响,这个标准公式是否不允许其他实现细节访问未初始化的dsb
我的问题是:这个标准保证上述代码是可移植的,即适用于所有符合标准的std::ostream实现吗?
1个回答

2
这个标准是否保证了上述代码是可移植的,即适用于所有符合标准的 std::ostream 实现?
是的。
在调用 std::ostream(&dsb) 的时候,DerivedOutputStreamdsb 成员已经被分配但未初始化。也就是说,我们可以获取它的地址,但我们不能读取其(未初始化的)值。
如问题中所链接的那样,[ostream.cons] 指定了 std::basic_ostream 的构造函数,特别地,你的示例中选择的是 [ostream.cons]/1
``` explicit basic_ostream(basic_streambuf* sb); 作用: 使用basic_ios::init(sb) ([basic.ios.cons])初始化基类子对象。 后置条件: rdbuf() == sb。 ```
`[tab:basic.ios.cons]`描述了调用`void init(basic_streambuf* sb)`的作用,其中一个特别是上面构造函数中描述的后置条件。
rdbuf() == sb
其中其余部分不对指向的底层缓冲区执行读取访问操作。
现在,rdbuf() 只是一个指针,因此这意味着通过值传递给 init 的 sb 指针(即指针本身)将用于初始化 rdbuf()。但是,在此时没有读取 sb 指向的底层缓冲区的副作用。

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

实际上,特别是对于 void init(basic_streambuf<charT, traits>*) 函数的实现需要尊重并不扩展 [tab:basic.ios.cons] 中明确定义的效果。
这篇(相当老的)文章...
请注意,[ostream.cons]的状态自C++诞生以来基本上没有改变;例如,我们可以查看{{link2:1994年9月C ++工作文件}}中的{{link1:[lib.input.output]}}。

27.2.4.1.1 basic_ostream constructor [lib.basic.ostream.sb.cons]

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

1 Constructs an object of class basic_ostream, assigning initial values to the base class by calling basic_ios<charT,baggage>::init(sb).

并且

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

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

1 The postconditions of this function are indicated in Table 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()调用进行相同委派并具有明确定义效果的过程。

你回答的关键点是,“Effects”在标准中意味着只允许这些效果。你能提供一个来源吗?例如,调用父构造函数也可能是一种效果。难道父构造函数没有被调用吗? - ead
我的对库规范的理解一直是,效果精确地描述了给定函数的操作,并且任何前提都明确说明(例如,输入迭代器或指针是否需要“可解引用”)。请参见一般结构描述,以及alg.swap的规范,在该规范中,要求输入迭代器在该函数的前提条件中被明确要求为可解引用(将减少未定义行为的发生)。 - dfrib
我认为,传递一个指向有效对象的指针是一个隐含的要求。例如,ifstream(const char* s, ...);并没有明确指定s必须是指向有效c字符串的指针 (http://eel.is/c++draft/ifstream#cons-2),但是传递一个指向某些(未初始化)内存的指针是一种未定义行为。 - ead
同时,“效果”并不一定描述所有可能的效果。例如,在一些(大多数?)basic_ostream的实现中,构造函数也会将存储gcount结果的成员变量初始化为0,但这是一个实现细节,在标准的“效果”下未提及。因此,我不确定是否可以遵循标准给出任何保证。 - ead

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