Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Does standard mandates that stream-constructors don't access stream buffer?

This (pretty old) article on iostreams and streambuf argues, that the following code is OK:

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

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

It might be problematic, because when ostream is constructed dsb isn't yet initialized and thus UB could be the effect. For the destructor it might be the other way around: dsb is already destructed and could be used in the destructor of ostream.

However, the article argues, "the C++ standard mandates that no parent class constructors or destructors (ios, istream, or ostream) access the stream buffer".

While the case of destructor is straight forward, e.g. for ~ostream:

virtual ~basic_ostream(); Remarks: Does not perform any operations on rdbuf().

It is less clear for constructor:

explicit basic_ostream(basic_streambuf<charT, traits>* sb); Effects: Initializes the base class subobject with basic_­ios<charT, traits>​::​init(sb) ([basic.ios.cons]).

Does it means this is the only possible effect, does this standard-formulation not allow for other effects, which are implementation details of std::ostream and could potentially access non-initialized dsb?

My question: Does that standard guarantees, that the above code is portable, i.e. works for all standard-compliant implementations of std::ostream?

like image 445
ead Avatar asked Oct 26 '22 17:10

ead


1 Answers

Does that standard guarantees, that the above code is portable, i.e. works for all standard-compliant implementations of std::ostream?

Yes.

At the point of calling std::ostream(&dsb) the dsb member of DerivedOutputStream has been allocated but not initialized. Meaning, we may take its address by we may not yet read its (uninitialized) value.

As linked in the question, the [ostream.cons] specifies the constructors of std::basic_ostream, and, particularly, the one chosen in you example is [ostream.cons]/1

explicit basic_ostream(basic_streambuf<charT, traits>* sb);

Effects: Initializes the base class subobject with basic_­ios<charT, traits>​::​init(sb) ([basic.ios.cons]).

Postconditions: rdbuf() == sb.

[tab:basic.ios.cons] describe the effects of invoking void init(basic_streambuf<charT, traits>* sb), one which is particularly the post-condition described in the constructor above:

rdbuf() == sb

and the rest of which does performs no read access to the underlying buffer to which sb points.

Now, rdbuf() is just a pointer, so this means the sb pointer passed to init by value (the pointer, that is) will be use to initialize rdbuf(). However, there is no side effect of reading the underlying buffer that sb points to, at this point.

[...] not allow for other effects, ...?

Indeed, an implementation for, particularly, the void init(basic_streambuf<charT, traits>*) function need to respect and not expand the well-specified effects of [tab:basic.ios.cons].


This (pretty old) article ...

Note that the state of [ostream.cons] has been basically unchanged since the dawn of C++ time; we may e.g. have a look at [lib.input.output] from the September 1994 C++ Working Paper

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).

and

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          |
               +----------------------------------+

which describe the same delegation via an init() call with well-defined effects.

like image 184
dfrib Avatar answered Nov 15 '22 09:11

dfrib