C++ Default Initialization and Value Initialization: Which Is Which, Which Is Called When and How to Reliably Initialize a Template-Type Member

C++ default initialization and value initialization: which is which, which is called when and how to reliably initialize a template-type member

Not so hard:

A x;
A * p = new A;

These two are default initialization. Since you don't have a user-defined constructor, this just means that all members are default-initialized. Default-initializing a fundamental type like int means "no initialization".

Next:

A * p = new A();

This is value initialization. (I don't think there exists an automatic version of this in C++98/03, though in C++11 you can say A x{};, and this brace-initialization becomes value-initialization. Moreover, A x = A(); is close enough practically despite being copy-initialization, or A x((A())) despite being direct-initialization.)

Again, in your case this just means that all members are value-initialized. Value initialization for fundamental types means zero-initialization, which in turn means that the variables are initialized to zero (which all fundamental types have).

For objects of class type, both default- and value-initialization invoke the default constructor. What happens then depends on the constructor's initializer list, and the game continues recursively for member variables.

Value-initializing an automatic object?

The following uses copy-initialization, which is 'probably fine' 95% of the time in C++03:

T var = T();

But for generic (C++03) code, you should always prefer direct-initialization to account for that other 5%:

T var((T())); // extra parentheses avoid the most vexing parse – the extra parentheses
// force the contents to be evaluated as an expression, thus implicitly
// *not* as a declaration.

Or better yet, use the Boost.Utility.ValueInit library, which packages up the ideal behavior for you along with workarounds for various compiler deficiencies (sadly, more than one might think):

boost::value_initialized<T> var;

For C++11, one can use list-initialization syntax to achieve direct value-initialization in a significantly less noisy/ugly manner:

T var{}; // unambiguously value-initialization†

(N.b. technically this will invoke std::initializer_list<> constructors instead of performing value-initialization for certain pathological types. Presumably the net result should be the same.)

What are primitive types default-initialized to in C++?

You are not correct. The object is not default-initialized but value-initialized. And its value is well-defined

int = 0, 
bool = false,
float = 0.0f,
enum = (enum type)0,
pointer = null pointer
pointer to member = null member pointer

Note that zero is in the range of values for any enumeration, even if it doesn't contain an explicit enumerator with that vaue, so it's safe to initialize an enumeration variable to that value.

In particular for pointer to data members, the representation used in practice is not all-zero bits. In the so-called C++ Itanium ABI used by at least GCC and Clang, pointer to data members have an all-one bits null representation.

C++ default value initialization changes with input-output streams

C++ initialization is super complicated. However, this case is rather simple (confusing terminology aside ;). You are right that both m and n are default initialized. Though, immediately after that your interpretation is rather off. It starts with

This is expected and corresponds to the fact that the long long type has a default initialization to 0 (zero initialization).

Default initialization for long long int is not zero initialization. Actually it is no initialization at all. From cppreference:

Default initialization is performed in three situations:

  1. when a variable with automatic, static, or thread-local storage duration is declared with no initializer;

[...]

and

The effects of default initialization are:

  • if T is a non-POD (until C++11) class type, [... long long is not a class type ...]
  • if T is an array type, [... long longis not an array type ...]
  • otherwise, no initialization is performed: the objects with automatic storage duration (and their subobjects) contain indeterminate values.

Reading an indeterminate value is undefined behavior. Adding or removing seemingly unrelated lines of code changing the output is typical effect of undefined behavior. The output of your code could be anything. It is undefined.

C++ zero initialization - Why is `b` in this program uninitialized, but `a` is initialized?

The issue here is pretty subtle. You would think that

bar::bar() = default;

would give you a compiler generated default constructor, and it does, but it is now considered user provided. [dcl.fct.def.default]/5 states:

Explicitly-defaulted functions and implicitly-declared functions are collectively called defaulted functions, and the implementation shall provide implicit definitions for them ([class.ctor] [class.dtor], [class.copy.ctor], [class.copy.assign]), which might mean defining them as deleted. A function is user-provided if it is user-declared and not explicitly defaulted or deleted on its first declaration. A user-provided explicitly-defaulted function (i.e., explicitly defaulted after its first declaration) is defined at the point where it is explicitly defaulted; if such a function is implicitly defined as deleted, the program is ill-formed. [ Note: Declaring a function as defaulted after its first declaration can provide efficient execution and concise definition while enabling a stable binary interface to an evolving code base. — end note ]

emphasis mine

So we can see that since you did not default bar() when you first declared it, it is now considered user provided. Because of that [dcl.init]/8.2

if T is a (possibly cv-qualified) class type without a user-provided or deleted default constructor, then the object is zero-initialized and the semantic constraints for default-initialization are checked, and if T has a non-trivial default constructor, the object is default-initialized;

no longer applies and we are not value initializing b but instead default initializing it per [dcl.init]/8.1

if T is a (possibly cv-qualified) class type ([class]) with either no default constructor ([class.default.ctor]) or a default constructor that is user-provided or deleted, then the object is default-initialized;

Does int() return 0 or an arbitrary value?

T() gives value initialization, which gives zero-initialization for a type other than a class, union or array. (§8.5/7 bullet 3): "otherwise, the object is zero-initialized." For an array, each element of the array is value initialized.

For an array, the content will be of an arbitrary value if it's auto storage class, but zero initialized if it's static storage class -- i.e., global (assuming, of course, that you don't specify any initialization).

Avoiding default construction of elements in standard containers

I think the problem boils down to the type of initialization that the container performs on elements. Compare:

T * p1 = new T;   // default-initalization
T * p2 = new T(); // value-initialization

The problem with the standard containers is that they take the default argument to be value initialized, as in resize(size_t, T = T()). This means that there's no elegant way to avoid value-initialization or copying. (Similarly for the constructor.)

Even using the standard allocators doesn't work, because their central construct() function takes an argument that becomes value-initialized. What you would rather need is a construct() that uses default-initialization:

template <typename T>
void definit_construct(void * addr)
{
new (addr) T; // default-initialization
}

Such a thing wouldn't be a conforming standard allocator any more, but you could build your own container around that idea.



Related Topics



Leave a reply



Submit