8

It's possible to make a local copy of an iostream object, using rdbuf and copyfmt. This allows formatting changes to be locally scoped:

std::ostream & operator << ( std::ostream & os, foo const & smth ) {
    cloned_ostream cs( os );
    cs << std::hex << smth.num;
    // os is not switched to hexadecimal, which would be a confusing side-effect
    return os;
}

Why don't the stream classes provide copy constructors to do this?

Have relevant C++ best practices changed since they were designed as non-copyable?

Potatoswatter
  • 134,909
  • 25
  • 265
  • 421
  • OK, I must be thinking of something else – M.M Mar 17 '16 at 04:31
  • You may have named that function clone, but I certainly wouldn't have. It certainly doesn't fit the semantics of the OO clone concept. – Benjamin Lindley Mar 17 '16 at 04:45
  • @BenjaminLindley Right, iostreams have reference semantics and this function typically copies a base slice. This is just an example. – Potatoswatter Mar 17 '16 at 04:51
  • 2
    `An istream object, for example, represents a stream of input values, some of which may have already been read, and some of which will potentially be read later. If an istream were to be copied, would that entail copying all the values that had already been read as well as all the values that would be read in the future? The easiest way to deal with such questions is to define them out of existence. Prohibiting the copying of streams does just that` --Effective Modern C++(Scott Meyers) – Praveen Mar 17 '16 at 05:01
  • They are not copyable because they have resources which cannot be shared without bringing huge overhead. To point this out: if you destroy `os` and then use `cs`, it would not have anywhere to write to. – StenSoft Mar 17 '16 at 05:03
  • @StenSoft That's not generally true. Some stream buffers live inside classes like `fstream`, other stream buffers are freestanding. The user should simply be informed that `fstream` and such destroy the associated buffer. Anyway, that's a matter of possibility or impossibility, not overhead. The overhead, as in this example, isn't much. – Potatoswatter Mar 17 '16 at 05:09
  • @Praveen That sounds like fallacious logic. `istream` represents *access to* an input sequence, not the values within the sequence. However, if that was what the designers were thinking, it might well be the answer. – Potatoswatter Mar 17 '16 at 05:13
  • Streams don't have an access protocol, the core reason that making them thread-safe was so expensive. So this is doomed to cause failure, copying an ostream likely causes random intermingling of output. Copy an istream likely causes random data loss to the other stream. – Hans Passant Mar 17 '16 at 06:53
  • @HansPassant Right… depending on what you do with the copy and the original, of course. I stumbled onto this topic from writing a [thread safety library](https://github.com/potswa/lock_ios). The idea being to provide a locked clone of a given stream. – Potatoswatter Mar 17 '16 at 15:18

1 Answers1

9

Copying and moving are value-semantic operations. To define them, you first have to decide what properties of a class give its objects distinct values. This point was at first largely sidestepped for the iostreams library, and then C++11 took a different direction incompatible with such a copy constructor.

The state of a stream object comprises two parts: A pointer to a stream buffer with its associated state, and formatting information. Since C++98, rdbuf, rdstate, and copyfmt expose this information separately.

Since C++11, stream classes also have a protected interface including a move constructor (and a member called move) which copies the format but not the stream buffer pointer. This commits iostream to treating the formatting information exclusively as the state of the stream object.

If streams were made copyable at this point, it would only do copyfmt and not the rest.

The choice to exclude rdbuf from the value state may be due to the further-muddled value semantics of derived classes such as std::fstream, which not only expose access to a stream buffer, but also embed and own it.

std::ifstream f( path + filename ); // Owns, or even "is," a file.
std::istream i = f; // Observes an externally-managed file.

std::istream i2 = i; // OK, copy a shallow reference.
std::ifstream f2 = f; // Error, ifstream is more than a shallow reference.

std::istream i3 = std::move( f ); // Error? Would retain a reference to an rvalue.
std::ifstream f3 = std::move( f ); // OK: full copy including the file buffer.

The semantics could be consistent in some fashion, but it would be a lot of confusion for a moderate gain.

Potatoswatter
  • 134,909
  • 25
  • 265
  • 421