C++标准是否保证特定ostream派生类代码具备可移植性?
先来看你提到的旧文章里的这段代码:
class DerivedStreamBuf : public std::streambuf { /* ... */ }; class DerivedOutputStream : public std::ostream { public: DerivedOutputStream(): std::ios(0), std::ostream(&dsb) {} // 标记为1的构造函数 // ... private: DerivedStreamBuf dsb; // ... };
你已经准确指出了这段代码的两个潜在风险:
- 构造
std::ostream时,成员变量dsb还没完成初始化(C++中基类构造函数先于成员变量执行,而dsb是类的成员,会在基类构造完成后才初始化),此时把&dsb传给ostream构造函数,会不会触发未定义行为? - 析构阶段,
dsb会先于基类ostream被销毁,ostream的析构函数会不会访问已经销毁的流缓冲?
旧文章提到“C++标准规定ios、istream或ostream的父类构造函数与析构函数不得访问流缓冲”,咱们结合标准来逐一验证:
析构函数:明确安全
首先说析构的情况,这部分很清晰。C++标准明确说明std::ostream的析构函数不会对rdbuf()返回的指针执行任何操作——它既不会调用流缓冲的析构函数,也不会访问其内部状态。所以哪怕dsb先于ostream销毁,ostream的析构过程也不会触碰这个已经失效的缓冲,完全没有问题。
构造函数:标准约束下的安全性
再看构造函数的问题。std::ostream的构造函数签名是:
explicit basic_ostream(basic_streambuf<charT, traits>* sb);
标准里对它的行为描述是:通过basic_ios<charT, traits>::init(sb)初始化基类子对象。这里的关键是:符合标准的实现能不能在构造函数里偷偷做其他访问sb的操作?
答案是绝对不行。C++标准对库函数的“效果”描述是强制性的——实现必须严格按照标准规定的行为来,不能额外添加超出描述的逻辑。而basic_ios::init(sb)的行为仅仅是:
- 将
sb存储为流的关联缓冲(后续可以通过rdbuf()获取) - 设置流的状态标志为
goodbit(除非sb是空指针,此时设置为badbit) - 不会调用
streambuf的任何成员函数,也不会解引用sb指针去访问其内部数据
也就是说,ostream的构造函数只是把dsb的地址存了起来,根本不会去访问dsb的任何未初始化内容。这时候哪怕dsb还没完成初始化,也只是存储了一个合法的内存地址(因为dsb的存储位置已经在类的内存布局中确定了,只是内部成员还没初始化),完全不会触发未定义行为。
当然要注意:如果在DerivedOutputStream的构造过程中,你自己写的其他代码(比如某个成员变量的初始化逻辑,或者基类构造的回调)试图去使用这个流缓冲,那肯定会出问题,但标准库本身的ostream和ios构造函数绝对不会这么做。
最终结论:代码具备可移植性
综合以上分析,这段代码是符合C++标准的,能在所有符合标准的编译器和标准库实现中正常运行:
- 构造阶段,
ostream仅存储缓冲地址,不访问未初始化的dsb; - 析构阶段,
ostream的析构函数不会触碰已销毁的缓冲。
不过实际工程中,这种写法容易让人误解初始化顺序的问题,可能会给后续维护带来困惑。如果想要更清晰的写法,可以考虑把dsb声明为static(如果不需要每个流实例都有独立缓冲),或者用动态分配的方式在构造ostream前创建缓冲,但从标准合规性和可移植性来说,原代码是完全没问题的。
内容的提问来源于stack exchange,提问作者ead

