简体   繁体   中英

What happens to the basic_ostream base when basic_iostream is moved?

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?

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM