421. is basic_streambuf copy-constructible?

Section: 30.6.3.1 [streambuf.cons] Status: NAD Submitter: Martin Sebor Opened: 2003-09-18 Last modified: 2016-02-10

Priority: Not Prioritized

View all other issues in [streambuf.cons].

View all issues with NAD status.

Discussion:

The reflector thread starting with c++std-lib-11346 notes that the class template basic_streambuf, along with basic_stringbuf and basic_filebuf, is copy-constructible but that the semantics of the copy constructors are not defined anywhere. Further, different implementations behave differently in this respect: some prevent copy construction of objects of these types by declaring their copy ctors and assignment operators private, others exhibit undefined behavior, while others still give these operations well-defined semantics.

Note that this problem doesn't seem to be isolated to just the three types mentioned above. A number of other types in the library section of the standard provide a compiler-generated copy ctor and assignment operator yet fail to specify their semantics. It's believed that the only types for which this is actually a problem (i.e. types where the compiler-generated default may be inappropriate and may not have been intended) are locale facets. See issue 439.

[ 2009-07 Frankfurt ]

NAD. Option B is already in the Working Draft.

Proposed resolution:

27.5.2 [lib.streambuf]: Add into the synopsis, public section, just above the destructor declaration:

basic_streambuf(const basic_streambuf& sb);
basic_streambuf& operator=(const basic_streambuf& sb);

Insert after 27.5.2.1, paragraph 2:

basic_streambuf(const basic_streambuf& sb);

Constructs a copy of sb.

Postcondtions:

                eback() == sb.eback()
                gptr()  == sb.gptr()
                egptr() == sb.egptr()
                pbase() == sb.pbase()
                pptr()  == sb.pptr()
                epptr() == sb.epptr()
                getloc() == sb.getloc()
basic_streambuf& operator=(const basic_streambuf& sb);

Assigns the data members of sb to this.

Postcondtions:

                eback() == sb.eback()
                gptr()  == sb.gptr()
                egptr() == sb.egptr()
                pbase() == sb.pbase()
                pptr()  == sb.pptr()
                epptr() == sb.epptr()
                getloc() == sb.getloc()

Returns: *this.

27.7.1 [lib.stringbuf]:

Option A:

Insert into the basic_stringbuf synopsis in the private section:

basic_stringbuf(const basic_stringbuf&);             // not defined
basic_stringbuf& operator=(const basic_stringbuf&);  // not defined

Option B:

Insert into the basic_stringbuf synopsis in the public section:

basic_stringbuf(const basic_stringbuf& sb);
basic_stringbuf& operator=(const basic_stringbuf& sb);

27.7.1.1, insert after paragraph 4:

basic_stringbuf(const basic_stringbuf& sb);

Constructs an independent copy of sb as if with sb.str(), and with the openmode that sb was constructed with.

Postcondtions:

               str() == sb.str()
               gptr()  - eback() == sb.gptr()  - sb.eback()
               egptr() - eback() == sb.egptr() - sb.eback()
               pptr()  - pbase() == sb.pptr()  - sb.pbase()
               getloc() == sb.getloc()

Note: The only requirement on epptr() is that it point beyond the initialized range if an output sequence exists. There is no requirement that epptr() - pbase() == sb.epptr() - sb.pbase().

basic_stringbuf& operator=(const basic_stringbuf& sb);

After assignment the basic_stringbuf has the same state as if it were initially copy constructed from sb, except that the basic_stringbuf is allowed to retain any excess capacity it might have, which may in turn effect the value of epptr().

27.8.1.1 [lib.filebuf]

Insert at the bottom of the basic_filebuf synopsis:

private:
  basic_filebuf(const basic_filebuf&);             // not defined
  basic_filebuf& operator=(const basic_filebuf&);  // not defined

[Kona: this is an issue for basic_streambuf itself and for its derived classes. We are leaning toward allowing basic_streambuf to be copyable, and specifying its precise semantics. (Probably the obvious: copying the buffer pointers.) We are less sure whether the streambuf derived classes should be copyable. Howard will write up a proposal.]

[Sydney: Dietmar presented a new argument against basic_streambuf being copyable: it can lead to an encapsulation violation. filebuf inherits from streambuf. Now suppose you inherit a my_hijacking_buf from streambuf. You can copy the streambuf portion of a filebuf to a my_hijacking_buf, giving you access to the pointers into the filebuf's internal buffer. Perhaps not a very strong argument, but it was strong enough to make people nervous. There was weak preference for having streambuf not be copyable. There was weak preference for having stringbuf not be copyable even if streambuf is. Move this issue to open for now. ]

[ 2007-01-12, Howard: Rvalue Reference Recommendations for Chapter 27 recommends protected copy constructor and assignment for basic_streambuf with the same semantics as would be generated by the compiler. These members aid in derived classes implementing move semantics. A protected copy constructor and copy assignment operator do not expose encapsulation more so than it is today as each data member of a basic_streambuf is already both readable and writable by derived classes via various get/set protected member functions (eback(), setp(), etc.). Rather a protected copy constructor and copy assignment operator simply make the job of derived classes implementing move semantics less tedious and error prone. ]

Rationale:

27.5.2 [lib.streambuf]: The proposed basic_streambuf copy constructor and assignment operator are the same as currently implied by the lack of declarations: public and simply copies the data members. This resolution is not a change but a clarification of the current standard.

27.7.1 [lib.stringbuf]: There are two reasonable options: A) Make basic_stringbuf not copyable. This is likely the status-quo of current implementations. B) Reasonable copy semantics of basic_stringbuf can be defined and implemented. A copyable basic_streambuf is arguably more useful than a non-copyable one. This should be considered as new functionality and not the fixing of a defect. If option B is chosen, ramifications from issue 432 are taken into account.

27.8.1.1 [lib.filebuf]: There are no reasonable copy semantics for basic_filebuf.