3

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

3 Effects: Move constructs from the rvalue rhs by constructing the basic_istream base class with move(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?

Community
  • 1
  • 1
Cubbi
  • 46,567
  • 13
  • 103
  • 169

2 Answers2

7

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

Howard Hinnant
  • 206,506
  • 52
  • 449
  • 577
3

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.

Bo Persson
  • 90,663
  • 31
  • 146
  • 203