Just when I thought I understand the multi-pass move construction of iostreams in C++11 (thanks to https://stackoverflow.com/a/8156356/273767 for the intro), I ran into this:
§27.7.2.5.1[iostream.cons]/3
basic_iostream(basic_iostream&& rhs);
3Effects: Move constructs from the rvalue rhs by constructing thebasic_istreambase class withmove(rhs).
So what happens to the other base, basic_ostream?
I see libc++ gave std::basic_ostream a protected default constructor, which is called here (and also, in contradiction to the letter of §27.7.2.5.1/1, in the normal constructor of basic_iostream), and does nothing. Is that how it's supposed to be?
As you point out, the spec for:
explicit basic_iostream(basic_streambuf<charT,traits>* sb);
initializes both bases. I've never been ok with that:
http://cplusplus.github.com/LWG/lwg-closed.html#135
as it causes the single basic_ios::init() function to be called twice on the same virtual base object. The committee ruled that this double initialization was harmless. I disagreed strongly enough that I refused to implement the specification in regards to this detail. But the spec says to double initialize the virtual base class.
When it came time to specify the basic_iostream move constructor, I was in the driver's seat. And so I specified it how I thought best (to not doubly initialize basic_ios). That decision has yet to be challenged, but probably will be eventually.
Note that in order to avoid the double initialization, the basic_ostream default constructor has to be carefully crafted to do absolutely nothing. And by nothing I really mean nothing. No zero initialization:
protected:
_LIBCPP_ALWAYS_INLINE
basic_ostream() {} // extension, intentially does not initialize
Fortunately the base classes of basic_ostream are actually specified to do nothing in their default constructors. So everything just works: basic_ostream default constructs and doesn't touch memory. Then derived clients call init(basic_streambuf<char_type, traits_type>*) exactly once to do the actual construction of basic_ios/ios_base.
It's a really messy design. By refusing to double initialize the virtual base, I feel libc++ makes the design a little less messy, and a little more reliable. This is standard behavior for the move constructor, and not standard behavior for the constructor taking a streambuf*.
I agree with Howard that this is kind of a wart on the standard spec.
In my implementation, I chose to use a more specific call to a constructor extension
basic_iostream(basic_iostream&& _Other)
: std::basic_istream<char_type, traits_type>(std::move(_Other)),
std::basic_ostream<char_type, traits_type>(basic_ostream::_NoInit)
{ }
using a special protected constructor in the base class
protected:
// special interface for basic_iostream
enum __no_init_t { _NoInit };
basic_ostream(__no_init_t)
{ }
The net effect is the same.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With