18

C++11 §27.5.4.2/21:

void swap(basic_ios& rhs);

Effects: The states of *this and rhs shall be exchanged, except that rdbuf() shall return the same value as it returned before the function call, and rhs.rdbuf() shall return the same value as it returned before the function call.

What is this partial swapping useful for?

Can it cause trouble?

Cheers and hth. - Alf
  • 142,714
  • 15
  • 209
  • 331
  • Alf P. Steinbach asking a question. Unbelievable. :| – Nawaz Nov 16 '11 at 07:18
  • This is really surprising. If I swap two things I'd really expect them to swap; if I'd had a bug and found it wasn't swapping `rdbuf`, I'd assume it was an implementation bug. – GManNickG Nov 16 '11 at 07:27
  • Quite surprising indeed. I checked the first FinalDraft I had (n3092) and it's exactly the same. I wonder if it was identical in C++03, maybe a legacy remnant ? – Matthieu M. Nov 16 '11 at 07:32
  • 2
    Does this make `swap` "inconsistent" with move assignment? Or does moving a stream leave the buffer behind too? If it's inconsistent, then there could be potential trouble if someone assumes in generic code that for any type `T`, the final result of `swap(t1,t2)` is the same as the final result of `T t3(move(t1)); t1 = move(t2); t2 = move(t3);`. – Steve Jessop Nov 16 '11 at 09:38

2 Answers2

22

You can blame me for this one. The committee has tried to change (twice I think), but each time the solution ended up breaking things.

Swap and move semantics was retrofitted onto our I/O system a decade after it was designed. And it wasn't a perfectly clean fit.

Note that basic_ios::swap is a protected member function and there is no namespace-scope variant. Thus this can only be called from a derived class (typically istream/ostream). Note that i/o_stream::swap is also protected and with no namespace-scope variant. And their spec is to call the base class swap and then swap any local data (such as the gcount in istream).

Finally up at the string/filestream level you get what you would consider a "normal" swap: public member and namespace-scope variants. At this level you've got a data member string/file buffer (the rdbuf) and the base class. The swap at this level simply swaps the base and data members.

The complicating characteristic of all this is that the rdbuf() down in the base class is actually a self-referencing pointer to the derived class's streambuf (basic_filebuf or basic_stringbuf) and that is why you don't want the base class to swap these self-referencing pointers.

This makes the base swap weird, but everyone is protected from it except the derived clients. And the code for the derived client's swap is subsequently deceptively simple looking. And at the derived level, swap is made public and behaves in the manner that public clients expect it to.

A similar dance is made for move construction and move assignment. Move construction is further complicated by the fact that the base class is a virtual base, and thus its constructor is not called by the most directly derived class.

It was fun. It looks weird. But it ultimately works. ;-)

Slight Correction:

Alberto Ganesh Barbati is responsible for protecting swap at the i/ostream level. It was a very good call on his part that I had completely missed with my first design.

Howard Hinnant
  • 206,506
  • 52
  • 449
  • 577
  • So, tl;dr: The base class doesn't know how to swap the derived class' buffer and that's why it leaves that to the derived classes? What about a virtual swap function in `basic_streambuf`? – Xeo Nov 16 '11 at 18:08
  • I haven't prototyped that design, but I'm guessing it could work. The spec at the derived level then looks a little weird though: swap your base class but not your data member. – Howard Hinnant Nov 16 '11 at 18:11
  • True, seems like either the base or derived specification has to look weird with the current implementation of iostreams. On a side-note, have you ever thought about redesigning iostreams or replacing them with something else entirely? I can see that this would hurt backwards compatability badly, but assuming that would be no problem.. are there ideas circulating through the committee, e.g. for inclusion in Boost or sth? – Xeo Nov 17 '11 at 02:31
  • 1
    I personally poured some time into a replacement I/O system a few years ago. But it never got further than a hobby. However I think there is a potential here. I'd love to see something that was naturally thread-safe internally (like C), and that handled localization in a more data-driven (rather than algorithm-driven) way (like C). But retain the type-safety and potential performance enhancements of type-safe-binding of C++. And with 20-20 hindsight, I would just assume Unicode and drop support for non-Unicode encodings. But I am not aware of an organized effort in this area. – Howard Hinnant Nov 17 '11 at 02:44
2

I only have one speculative answer...

If the author assumed that a stream may use an internal buffer (for example a char buffer[50] data member), then this provision is necessary as obviously the content of the buffers may be swapped, but their address will remain unchanged.

I do not know whether it is actually allowed or not.

Matthieu M.
  • 287,565
  • 48
  • 449
  • 722
  • you can replace buffer via `rdbuf` function. – Cheers and hth. - Alf Nov 16 '11 at 07:41
  • @AlfP.Steinbach: Yes, but where does the original buffer comes from ? It's bound to be allocated somewhere, and I don't find anything preventing me from using an internal array for it. – Matthieu M. Nov 16 '11 at 07:46
  • 1
    the buffer is an object of type `basic_streambuf`, or derived. it can be replaced via `stream.rdbuf( pMyNewBuffer )`. the `basic_ios` destructor is documented as not destroying the `rdbuf()` object, so it's entirely possible to put that object as a direct member, but the class still has to support changing the `rdbuf()` pointer. – Cheers and hth. - Alf Nov 16 '11 at 07:55
  • 1
    @AlfP.Steinbach: I didn't say you could not change it. `struct S { char buffer[64]; char* ptr; };` with `ptr` originally pointing at `buffer` allows to change `ptr` while still having an inner buffer to start with. – Matthieu M. Nov 16 '11 at 08:13
  • Thinking about it yes, with an ordinary full swap allowing the direct member buffer would prevent swapping between objects with different lifetimes. One object's buffer pointer could then become dangling at some point. However, if I really needed `swap` for a class with such issue, then I would solve that by requiring the buffer to be dynamically allocated and imposing some lifetime management, not by defining a partial `swap`? – Cheers and hth. - Alf Nov 16 '11 at 09:00
  • Sounds like it must be that the committee rated the flexibility of there being no lifetime management, more highly than a "proper" swap. You can't have both, and it's too late to make `rdbuf` take a `shared_ptr` (or `unique_ptr`) instead of a buffer, which would be useful in that you get lifetime management without a requirement for dynamic allocation. – Steve Jessop Nov 16 '11 at 09:36
  • @AlfP.Steinbach: I don't pretend to read minds :) Also, the text clearly states that the state should be exchanged, so even though the `rdbuf` call returns the same pointer, I think that the content of the `stream_buf` has been swapped nonetheless... Definitely weird :x – Matthieu M. Nov 16 '11 at 12:29