basic_ios::init
call restrictionsSection: 31.5.4.2 [basic.ios.cons] Status: Open Submitter: Andrey Semashev Opened: 2012-11-09 Last modified: 2021-07-31
Priority: 4
View all other issues in [basic.ios.cons].
View all issues with Open status.
Discussion:
There is an ambiguity in how std::basic_ios::init
method (31.5.4.2 [basic.ios.cons])
can be used in the derived class. The Standard only specify the state of the basic_ios
object after the call completes. However, in basic_ios
default constructor description
(31.5.4.2 [basic.ios.cons]) there is this sentence:
Effects: Constructs an object of class
basic_ios
(31.5.2.8 [ios.base.cons]) leaving its member objects uninitialized. The object shall be initialized by callingbasic_ios::init
before its first use or before it is destroyed, whichever comes first; otherwise the behavior is undefined.
This restriction hints that basic_ios::init
should be called exactly
once before the object can be used or destroyed, because basic_ios::init
may not know whether it was called before or not (i.e. whether its members are actually
uninitialized or are initialized by the previous call to basic_ios::init
). There
is no such restriction in the basic_ios::init
preconditions so it is not clear whether it is
allowed to call basic_ios::init
multiple times or not.
basic_ios::init
is called multiple times, while GCC 4.7 and STLPort
reinitialize the basic_ios
object correctly without memory leak or any
other undesired effects. There was a discussion of this issue on Boost
developers mailing list,
and there is a test case
that reproduces the problem. The test case is actually a bug report for my Boost.Log library,
which attempts to cache basic_ostream
-derived objects internally to avoid expensive construction
and destruction. My stream objects allowed resetting the stream buffer pointers the stream
is attached to, without requiring to destroy and construct the stream.
My personal view of the problem and proposed resolution follows.
While apparently the intent of basic_ios::init
is to provide a way to
initialize basic_ios
after default construction, I see no reason to
forbid it from being called multiple times to reinitialize the stream.
Furthermore, it is possible to implement a conforming basic_ios
that
does not have this restriction.
The quoted above section of the Standard that describes the effects of
the default constructor is misleading. The Standard does not mandate
any data members of basic_ios
or ios_base
(31.5.2 [ios.base]), which
it derives from. This means that the implementation is allowed to use
non-POD data members with default constructors that initialize the
members with particular default values. For example, in the case of
Microsoft Visual C++ STL the leaked memory is an std::locale
instance
that is dynamically allocated during basic_ios::init
, a raw pointer to
which is stored within ios_base. It is possible to store e.g. an
unique_ptr
instead of a raw pointer as a member of ios_base
, the smart
pointer will default initialize the underlying raw pointer on default
construction and automatically destroy the allocated object upon being
reset or destroyed, which would eliminate the leak and allow
basic_ios::init
to be called multiple times. This leads to conclusion
that the default constructor of basic_ios
cannot leave "its member
objects uninitialized" but instead performs default initialization of
the member objects, which would mean the same thing in case of POD types.
However, I feel that restricting ios_base
and basic_ios
members to
non-POD types is not acceptable. Since multiple calls to basic_ios::init
are
not forbidden by the Standard, I propose to correct the basic_ios
default
constructor description so that it is allowed to destroy basic_ios
object
without calling basic_ios::init
. This would imply that any raw members of
basic_ios
and ios_base
should be initialized to values suitable for
destruction (essentially, this means only initializing raw pointers to NULL). The new
wording could look like this:
Effects: Constructs an object of class
basic_ios
(31.5.2.8 [ios.base.cons]) initializing its member objects to unspecified state, only suitable forbasic_ios
destruction. The object shall be initialized by callingbasic_ios::init
before its first use; otherwise the behavior is undefined.
This would remove the hint that basic_ios::init
must be called exactly
once. Also, this would remove the requirement for basic_ios::init
to
be called at all before the destruction. This is also an important issue because
the derived stream constructor may throw an exception before it manages to call
basic_ios::init
(for example, if the streambuf constructor throws), and
in this case the basic_ios
destructor has undefined behavior.
basic_ios::init
multiple times, a remark
or a footnote for basic_ios::init
postconditions could be added to explicitly
state the semantics of calling it multiple times. The note could read as follows:
The function can be called multiple times during the object lifetime. Each subsequent call reinitializes the object to the described in postconditions initial state.
[2013-04-20, Bristol]
Alisdair: The current wording is unclear but the proposed resolution is wrong
Solution: Clarify thatinit
must be called once and only once. Move then to review.
[2021-07-29 Tim comments]
The requirement that "init
must be called once and only once" conflicts
with the disposition of LWG 135.
Proposed resolution:
This wording is relative to N3485.
Edit 31.5.4.2 [basic.ios.cons] as indicated:
basic_ios();-2- Effects: Constructs an object of class
basic_ios
(31.5.2.8 [ios.base.cons])leaving its member objects uninitializedinitializing its member objects to unspecified state, only suitable forbasic_ios
destruction. The object shall be initialized by callingbasic_ios::init
before its first useor before it is destroyed, whichever comes first; otherwise the behavior is undefined.void init(basic_streambuf<charT,traits>* sb);Postconditions: The postconditions of this function are indicated in Table 128.
-?- Remarks: The function can be called multiple times during the object lifetime. Each subsequent call reinitializes the object to the described in postconditions initial state.