标准是不是要求流构造函数不访问流缓冲区?
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)
时,DerivedOutputStream
的dsb
成员已分配,但未初始化。意思是,我们可以通过我们可能还没有读取它的(未初始化的)值来获取它的地址。
正如问题中所链接的,[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<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 该函数的后置条件如表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,但这是一个实现细节,标准中的“效果”下未提及.所以我不确定是否可以遵循,该标准提供了任何保证。以上是关于标准是不是要求流构造函数不访问流缓冲区?的主要内容,如果未能解决你的问题,请参考以下文章
文件流之字节缓冲流(BufferedInputStream BufferedOutputStream)