What problem does it solve that for not non-vacuous initialization, lifetime begins before initialization?

Viewed 286

Current draft standard says (previous standards have similar wording) in [basic.life/1]:

The lifetime of an object or reference is a runtime property of the object or reference. An object is said to have non-vacuous initialization if it is of a class or aggregate type and it or one of its subobjects is initialized by a constructor other than a trivial default constructor. [ Note: Initialization by a trivial copy/move constructor is non-vacuous initialization. — end note ] The lifetime of an object of type T begins when:

(1.1) storage with the proper alignment and size for type T is obtained, and

(1.2) if the object has non-vacuous initialization, its initialization is complete

Why is it important here, that the initialization is non-vacuous? In other words, why does the lifetime begin before initialization is complete, if the initialization is not non-vacuous? What problem does it solve?

What would be the consequences, if (1.2) would read as

(1.2) if the object has initialization, its initialization is complete

The term non-vacuous is only used here, it must have some reason.


Note, there was a similar previous question of mine here, but as that question is a little bit too broad (and even I accepted one of the answers), the responses there don't answer this current question.

3 Answers

I think that it's meant to work around the default initialization that performs no initialization:

[dlc.init]:

To default-initialize an object of type T means:

(7.1) — If T is a (possibly cv-qualified) class type (Clause 12), constructors are considered. The applicable constructors are enumerated (16.3.1.3), and the best one for the initializer () is chosen through overload resolution (16.3). The constructor thus selected is called, with an empty argument list, to initialize the object.

(7.2) — If T is an array type, each element is default-initialized.

(7.3) — Otherwise, no initialization is performed.

I understand that all objects have initialization, but for some objects with default initialization no initialization is performed. Please note that it specifically says that the "initialization is not performed", not that it does not exist.

You can have a default-initialized object for which no initialization was performed, thus it could not complete. They would not begin their life time, and with current wording they don't have to wait for this no-init to happen for their inception. Also such object has to have initialization, because it's default-initialized.

IMHO your wording would have a flaw or you understanding would cause logical consequeces for 7.3 of default-inialization definition because such objects would either:

  1. Have initialization and would not start to live, because it wasn't performed.
  2. Be default-initialized but would have no initialization. I understand it could be confusing.

With current wording it's pretty clear. Maybe some place could explicitly say which objects have initialization or what does it mean to have initialization. I think all do, but I don't have a pragraph for it under my hand.

The difference between the Standard quote involving the "non-vacuous initialization" definition and your alternate sentence without "non-vacuous" is that the Standard sentence does allow some cases where an object's lifetime begins before its initialization is complete.

There are three ways (I can think of) an object could possibly be used before its initialization is complete:

  1. The object is defined at namespace scope or as a static class data member, and therefore has static storage duration and a name which can be used from nearly any scope. The object also has dynamic initialization.

  2. The object's name is used within its own initializer.

  3. The object is accessed via this (possibly implicit) within a constructor.

#2 is uncommon, and #3 is always ruled out here as a non-vacuous initialization, so let's look at #1. #1 is the exact setup for the Static Initialization Order Fiasco, but by the "non-vacuous" wording, the Standard says that some of these cases are actually okay:

int f();
int n = f();
int a[3] = {f(), f(), f()};
struct X {};
struct Y { int p; int q[2]; X x; };
Y y = { f(), { f(), f() }, {} };

Here all the declared objects have both zero-initialization and dynamic initialization. They can be used during dynamic initialization of another object with static storage duration defined earlier in the same translation unit (or during their own initializers) because their lifetimes have already begun, and the values of all int objects will be zero. They can be used during dynamic initialization of an object defined in a different translation unit, and the behavior is unspecified, not undefined, with the int values being either zero or the return value of the appropriate f() expression.

For example,

int i = i;

The lifetime of the object i refers to begins when evaluating the intializer i, so this line of code is well-defined (though i will have an indeterminate value, and it is undefined to use the value).


Another somewhat meaningful example:

struct A {
    int i, j;
    int square()
    {
        return i * i;
    }
};

A a{0, a.square()}; 

This is well-defined. Without the "non-vacuous" limitation, this would be undefined because a is used to call a non-static member function before the lifetime of a begins.

Related