JTC1/SC22/WG21
N0725
Accredited Standards Committee X3 Doc No: X3J16/95-0125 WG21/N0725
Information Processing Systems Date: Jun 30, 1995
Operating under the procedures of Project: Programming Language C++
American National Standards Institute Ref Doc:
Reply to: Josee Lajoie
(josee@vnet.ibm.com)
Initialization Issues and Proposed Resolutions
==============================================
o Initialization
================
*Issue 476:
----------
8.5p6 says:
"If no initializer is specified for an object with automatic or
dynamic storage duration, the object and its subobjects, if any,
have an indeterminate initial value."
The draft doesn't say that referring to an object that has
indeterminate value results in undefined behavior.
Proposal 476:
-------------
Add the following text at the end of 8.5 paragraph 6:
"Referring to an object with an indeterminate value results in
undefined behavior."
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
*Issue I1:
---------
Tthe first paragraph 8.5.1[dcl.init.aggr] excludes classes with
nonstatic reference or const members from being aggregates.
What's wrong with:
struct S { const int member; } object = { 0 };
or
int i;
struct T { int &r; } t = { i };
?
Proposal I1:
------------
After the core work on initialization at the Austin meeting,
paragraph 1 of 8.5.1 [dcl.init.aggr] was:
"An aggregate is an array or a class (_class_) with no
user-declared constructors (_class.ctor_), no private or protected
non-static data members (_class.access_), no base classes
(_class.derived_), and no virtual functions (_class.virtual_)."
I propose that we go back to this wording.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
*Issue Box 15:
-------------
3.6.1[basic.start.main] paragraph 1 says:
"A program shall contain a global function called main, which is
the designated start of the program."
Must all C++ program defined main?
In C, a freestanding environment does not require main; the program
invocation is implementation-defined. Should this be the case in C++?
Proposal Box 15:
----------------
Neal Gafter answers this question in Box 15.
His answer should be incorporated into the WP as a note.
And maybe add this as an item in appendix C (C compatibility)
"[Note: Unlike C, a C++ freestanding environment requires that main
be defined. This guarantees that the semantics of program start
and termination (in particular the construction and destruction of
objects with static storage duration) behaves as defined in this
International Standard].
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
*Issue Box 16:
-------------
3.6.1[basic.start.main] paragraph 4 says:
"Calling the function [exit] declared in <cstdlib> terminates the
program without leaving the current block and hence without
destroying any objects with automatic storage duration."
Why is an implementation prohibited from destroying objects with
automatic storage duration when exit is called?
Could it say it is implementation-defined?
Could the standard require that the implementation destroy objects
with automatic storage duration when exit is called?
Proposal Box 16:
----------------
I believe prohibiting implementations from destroying objects with
automatic storage duration when exit is called is too limiting.
Replace 3.6.1[basic.start.main] paragraph 4 with:
"Calling the function [exit] declared in <cstdlib> terminates the
program leaving the current block and hence destroying the objects
with automatic storage duration."
This is a bit radical... however, I choose this option because this
offers the greatest guarantee for users.
The fall back position is to leave this as implementation-defined,
in the case some implementations find this requirement unacceptable
because of performance costs.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
*Issue 462:
----------
What happens if 'exit' is called from a destructor?
What happens if a destructor throws an exception and the user-defined
terminate routine calls 'exit'?
Proposal 462:
-------------
The draft should say that calling 'exit' from a destructor results in
undefined behavior.
Add to 3.6.1 [basic.start.main] paragraph 4:
"If 'exit' is called during the destruction at block exit of
objects with automatic storage duration or during the destruction
at the end of the program of objects with static storage duration,
the program results in undefined-behavior."
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
*Issue I2:
---------
3.6.2[basic.start.init] paragraph 1 says:
"The initialization of nonlocal objects with static storage duration
defined in a translation unit shall be done before the first use
of any function or object defined in that translation unit."
It was suggested that the above wording be clarified as follows:
Proposal I2:
------------
"The initialization of nonlocal objects with static storage duration
defined in a translation unit shall be done before the first use
(where a use follows the invocation of 'main' and does not take
into account any usage within initializations) of any function or
object defined in that translation unit."
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
*Issue 430:
----------
What is the order of destruction of local static variables?
3.6.3[basic.start.term] paragraph 1 says:
"Destruction is done [for objects of static storage duration] in
reverse order of initialization."
6.7[stmt.dcl] says:
"The destructor is called ... exactly when is unspecified."
Discussion 430:
---------------
The possibilities:
1) The order of destruction of local variables with static storage
duration is unspecified. [as 6.7 implies].
2) It is guaranteed that local variables with static storage duration
declared in the same function are destroyed in reverse of
construction. The order of destruction of local variables with
static storage duration is otherwise unspecified.
3) It is guaranteed that variables with static storage duration
declared in the same translation unit (either declared at block
scope or declared in namespace scope) are destroyed in reverse of
construction. The order of destruction of local variables with
static storage duration is otherwise unspecified.
4) It is guaranteed that all variables with static storage
duration in the program (either declared at block scope or
declared in namespace scope) are destroyed in reverse of
construction. [as 3.6.3 implies].
4) is the nicest option for users. However, it is the most expensive
option for implementations. Mike Ball has commented earlier that
this option does not work well for dynamic libraries. However, the
draft does not describe the behavior of programs using dynamic
libraries in many other situations. The committee's position has been
to leave the description of programs using dynamic libraries as
implementation-defined. Maybe here is a situation where this applies
as well: the order of destruction of local variables with static
storage duration if the program uses dynamic libraries is
implementation-defined.
Proposal 430:
-------------
Adopt option 4).
The options seem less and less as the number gets smaller.
3.6.3[basic.start.term] paragraph 1 should say:
"Destruction of objects with static storage duration declared in
namespace scope or in block scope is done in reverse order of
initialization."
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
*Issue 484:
----------
When must objects with static storage duration be destroyed wrt to
calls to functions registered with atexit?
3.6.3[basic.start.term] paragraph 1 says:
"If atexit is to be called, the implementation shall not destroy
objects initialized before an atexit call until after the function
specified in the atexit() call has been called."
6.7[stmt.dcl] paragraph 5 says:
"The destructor [for local object with static storage duration]
is called either immediately before or as a part of the calls to
the atexit functions."
18.3[lib.support.start.term] paragraph 3 says:
"-- First all functions f registered by calling atexit(f) are called,
in reverse order of their registration.
-- Next, all static objects are destroyed in the reverse order of
their construction. ..."
Discussion 484:
-------------
I see two options:
1) all functions registered with atexit must have completed before
any destructor for objects with static storage duration (declared
either at block scope or at namespace scope) is called.
(in reverse order of construction, as described in issue 430).
2) Allow what 3.6.3 seems to imply:
"An implementation shall not destroy objects with static storage
duration initialized before an atexit call until after the function
specified in the atexit() call has been called."
This second option allows calls to the destructors for objects
with static storage duration to preceed calls to functions
registered with atexit, as long as the construction of the object
with static storage duration completed after the function was
registered with atexit.
Proposal 484:
-------------
Adopt solution 1.
I prefer this solution because it gives example B) below a more
intuitive behavior.
3.6.3[basic.start.term] paragraph 1 should say:
"If atexit is to be called, the implementation shall not destroy
objects of static storage duration until after the functions
specified in the atexit() call have been completed."
6.7[stmt.dcl] paragraph 5 should say:
"The destruction of local object with static storage duration
follows the rules described in 3.6.3."
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Examples [from Jerry Schwarz in core-5596]:
A) What is the correct thing to happen if atexit is called during
construction of a static object?
Proposal:
With the proposals above, all objects with static storage duration
are destroyed after the calls to the functions registered with
atexit have completed. Since destruction cannot take place as part
of the calls to the functions registered with atexit, the timing of
the construction of an object with static storage duration wrt call
to atexit does not matter.
B) What should happen if a static object is constructed during a call
to a function that was registered via atexit?
void f() {
static T t(1);
}
void g() {
static T u(2);
}
main() {
atexit(f);
atexit(g);
f(); // ensures 't' is constructed, 'u' is not yet
exit(0); // now what? calls 'g' and then 'f'
// two questions arise:
// 1) is 'u' destroyed?
// (it wasn't constructed prior to the atexit function
// calls)
// 2) has 't' been destroyed prior to calling 'f' again?
}
Proposal:
Since objects with static storage duration are destroyed after all
functions registered with atexit have completed, upon exit of the
program,
1) the functions registered with atexit are first executed:
1) g() [called first]
2) f()
2) objects with static storage duration are then destroyed,
in reverse order of construction:
1) u [is destroyed first]
2) t
C) What if exit is called from the initialization of a variable with
static storage duration before main even begins to run?
Proposal:
The objects with static storage duration for which construction
has completed are destroyed in reverse order of construction.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
*Issue 521:
----------
3.6.3[basic.start.term] paragraph 3 says:
"Calling the function [abort] terminates the program without
executing destructor for static objects and without calling the
functions passed to atexit()."
This should be clearer to indicate that the destructors for local
objects with automatic storage duration are not called either.
Proposal 521:
-------------
Change the sentence above to say:
"Calling the function [abort] terminates the program without
calling the destructor for objects of automatic or static storage
duration and without calling the functions registered with
atexit()."
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
*Issue 429:
----------
Is the following initialization of "r" performed "statically" (that
is, at link time) or "dynamicaly" (that is, at its turn among
dynamically initialized nonlocal objects in the translation unit)?
extern int x;
int &r = x;
Proposal 429:
-------------
If we want some form of reference initialization to take place
during the phase of constant initialization, another category of
"constant expressions" is needed in 5.19[expr.const].
Something like this:
"A 'reference constant expression' is an lvalue designating an
object of static storage duration or a function. The subscripting
'[]' operator, the class member access '.' and '->' operators, the
'&' and '*' unary operators, and reference casts (except those
invoking user-defined conversion functions (12.3) and except
dynamic_casts (5.2.6) can be used by the lvalue expression of a
reference constant expression, but the value of an object shall not
be accessed by the use of these operators. An lvalue expression
that designates a member or base class of a non-POD class object
(9) is not a reference constant expression (12.7). Function calls
shall not be used in a reference constant expression, even if the
function is inline and has a reference return type."
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
*Issue 485:
----------
12.4[class.dtor] paragraph 5 says:
"Bases and members are destroyed in reverse order of their
construction."
Since construction takes place over time, is the order to be reversed
the one when the constructor begins or when it finishes.
Proposal 485:
-------------
The one when it finishes.
Change 12.4 paragraph 5 to say:
"Bases and members are destroyed in reverse order of the completion
of their construction."
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
*Issue 359:
----------
12.6.2[class.base.init] describes the order in which the members and
base class parts of an object of class type are initialized. It does
not, however, specify when the expressions used in the
"mem-initializers" are to be evaluated.
Example:
struct S {
int i;
int j;
S() : i(0), j(i) {}
};
12.6.2 paragraph 1 requires that i be initialized before j. However,
the rules in 1.8[intro.execution] indicate that, since there is no
function call (constructor call) to initialize the member i, there is
no sequence point between the evaluation of the initializer for i and
the evaluation of the initializer for j. The current rules therefore
permit the following order of execution:
- Fetch the (uninitialized) value of i
- Initialize i to 0
- Initialize j to the previously fetched value of i which is
probably not what the programmer intended.
Discussion 359:
---------------
There are two possible solutions:
1) Keep the status quo.
2) Adopt a new rule that would force a sequence point between the
evaluation of the initializers in the ctor-initializer list.
Here is the new wording for option 2 suggested by Patrick Smith:
"The expressions in the expression-list part of a mem-initializer
are evaluated (including all side effects) immediately before the
corresponding initialization is performed. When one base class or
member is initialized before another base class or member, the
expressions used in the mem-initializer for the second are
evaluated (including all side effects) after the first
initialization is performed."
Proposal 359:
-------------
Keep the status quo (option 1).
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
*Issue Box 46:
-------------
Should the standard mandate that const members and reference members
be initialized in the ctor-initializer of their owning class?
Example:
struct B {
const int i;
B();
};
B() { } // is this ill-formed because the const member 'i' is not
// initialized by the constructor ctor-initializer list?
Proposal Box 46:
----------------
Since the standard requires that complete objects that are of const
or reference type be initialized, it seems to make sense that
constructors and initializer lists for classes with const or reference
members provide initializers for these members.
Replace 12.6.2[class.base.init] paragraph 3:
"Note: when a constructor creates an object of class type X, if X
has a nonstatic data member m that is of const or reference type
and if the member is neither specified in a mem-initializer nor
eligible for default-initialization (8.5), then m will have an
indeterminate value."
with:
"The definition for a constructor for a class X with a nonstatic
data member m of const or reference type shall either specified
a mem-initializer for m or m shall be of a class type with a
user-declared default constructor, otherwise the constructor
definition is ill-formed."
Similarly, the following text should be added to 8.5.1[dcl.init.aggr],
at the end of paragraph 2:
"An initializer list for an aggregate X with a nonstatic data
member m of const or reference type shall either provide an
initializer for m or m shall be of a class type with a
user-declared default constructor, otherwise the initializer list
is ill-formed."
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
*Issue I3:
---------
In Message c++std-core-5700, Erwin Unruh writes:
"It should be described that a destructor calls all destructors
for direct bases, virtual bases and members."
Proposal I3:
------------
12.4[class.dtor] paragraph 5 should say:
"A destructor calls all destructors for direct bases, virtual bases
and members. Bases and members are destroyed in reverse order of
construction (see 12.6.2)."
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
*Issue 499:
----------
When are temporaries destroyed when created by the left expression of
the comma operator?
5.18[expr.comma] paragraph 1 says:
"All side effects of the left expression are performed before the
evaluation of the right expression."
This seems to contradict the rule that requires temporaries to be
destroyed at the end of full expressions.
Proposal 499:
-------------
Change the sentence above as follows:
"All side effects of the left expression, except for destruction of
temporaries (12.2), are performed before the evaluation of the right
expression."
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
*Issue 477:
----------
When can an implementation create temporaries?
12.2[class.temporary] paragraph 2 contains the phrase
"In some circumstances it might be necessary or convenient for an
implementation to generate a temporary object. Precisely when
such temporaries are introduced is implementation-defined".
This seems extremely vague. For example, is the implementation
allowed to introduce a temporary of type Foo in the following example?
class Foo {
private:
Foo();
};
int main() {
return 0;
}
I believe everyone will agree that the implementation is not allowed
to introduce a temporary of type `Foo' in the example above and not
allowed to reject the program as ill-formed. The WP text should be
clarified to indicate this.
Proposal 477:
-------------
Change the sentences above to say:
"While evaluating an expression, it might be necessary or
convenient for an implementation to generate temporary objects to
hold values resulting from the evaluation of the expression's
subexpressions. During this evaluation, precisely when such
temporaries are introduced is unspecified."
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
*Issue 516:
----------
What is the lifetime of "helping" temporaries?
12.2[class.temporary] paragraph 4 says:
"The first context is when an expression appears as an initializer
for a declarator defining an object. In that context, the
temporary that holds the result of the expression shall persist
until the object's intialization is complete."
All temporaries created in the evaluation of the expression should
last until the object is initialized. It should NOT be limited to the
result temporary. For example:
char *a = string().c_str;
12.2p5 says:
"The temporary bound to a reference or the temporary containing the
sub-object that is bound to the reference persists for the
lifetime of the reference initialized..."
It should be made explicit, whether helping references also last as
long as the reference.
char * &ra = string().c_str;
Does the string temporary last as long as the reference?
[answer: Yes, because the temporary of type string contains the member
subobject c_str bound to the reference ra.
Proposal 516:
-------------
Change the sentences in 12.2[class.temporary] paragraph 4 to say:
"The first context is when an expression appears as an initializer
for a declarator defining an object... In that context, the
temporaries created while evaluating the initializer expression
shall persist until the object's intialization is complete."
Change the sentences in 12.2[class.temporary] paragraph 5 to say:
"The temporary bound to a reference or the temporary containing the
subobject that is bound to the reference persists for the lifetime
of the reference initialized or until the end of the scope in which
the temporary is created, which ever comes first. The other
temporaries created while evaluating the initializer expression for
the reference are destroyed at the end of the the initializer's
full expression."
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
*Issue 509:
----------
How does "destroy in reverse order of creation" work for temporaries
bound to references vs temporaries destroyed at the end of the
initialization?
12.2[class.temporary] paragraph 6:
"In all cases, temporaries are destroyed in reverse order of
creation."
This is not quite right.
Temporaries are destroyed in reverse order of creation, except those
temporaries created in an expression used to initialize a reference;
these are destroyed when the reference is destroyed, in reverse order
of creation.
Proposal 509:
-------------
Add at the end of paragraph 4:
"In that context, the temporaries created while evaluating the
initializer expression shall persist until the object's
initialization is complete. These temporaries shall be destroyed
in reverse order of creation."
Add at the end of paragraph 5:
"In each of these situations, the temporaries shall be destroyed in
reverse order creation."
Delete paragraph 6.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
After all these changes to 12.2[class.temporary]
paragraph 4 reads:
"The first context is when an expression appears as an initializer
for a declarator defining an object. The object is initialized
from a copy of the temporary holding the result value of the
initializer expression. During this copying, an implementation can
call the copy constructor many times. In that context, the
temporaries created while evaluating the initializer expression
shall persist until the object's initialization is complete. These
temporaries shall be destroyed in reverse order of creation."
An example is needed to make things clearer.
paragraph 5 reads:
"The temporary bound to a reference or the temporary containing the
subobject that is bound to the reference persists for the lifetime
of the reference initialized or until the end of the scope in which
the temporary is created, which ever comes first; the other
temporaries created while evaluating the initializer expression for
the reference are destroyed when the initialization is complete,
in reverse order of creation. A temporary holding the result of
an initializer expression for a declaration that declares a
reference persists until the end of the scope in which the
reference declaration occurs. A temporary bound to a reference in
a constructor's ctor-initializer (12.6.2) persists until the
constructor exits. A temporary bound to a reference parameter in a
function call (5.2.2) persists until the completion of the complete
expression containing the call. A temporary bound in a function
return statement (6.6.3) persists until the function exits. In
each of these situations, the temporaries shall be destroyed in
reverse order creation."
An example is needed to make things clearer.