Initialization gives an entity its first value or establishes a reference binding. Assignment changes an object that already exists. Because constructors participate only in initialization and assignment operators participate only after lifetime has begun, the distinction affects overload resolution, invariants, and performance.
C Background #
A C declaration has declarators, each optionally followed by = initializer. An initializer is an assignment expression or a braced initializer list; nested braces initialize nested aggregates.
struct point { int x; int y; } points[] = {
[0] = {.x = 1},
[2] = {.y = 4},
};C aggregate initialization fills omitted elements as if they had static storage duration: arithmetic values become zero, pointer values become null pointers, and nested aggregates are initialized recursively. An automatic scalar declared with no initializer, by contrast, has an indeterminate value and must not be read before a value is stored.
C++ Forms and Grammar #
C++ lets a declarator carry an equals initializer, a braced initializer, or a parenthesized initializer. Consequently, these declarations have different forms:
T copy = expression; // Copy-initialization
T direct(expression); // Direct-initialization
T list{expression}; // Direct-list-initialization
T copy_list = {expression}; // Copy-list-initialization
A braced-init-list is not an expression and has no type. The destination type and the initialization form determine overload resolution, aggregate rules, and narrowing checks. T object() in a declaration is a function declaration, not an empty object; use T object{}; for an object.
Fundamental Categories #
Zero-Initialization #
Zero-initialization sets scalar values to their zero values and pointer values to null pointers; arrays are processed element by element. It occurs as part of static and thread storage initialization and some value-initialization paths. It is not a standalone spelling for a reference, because every reference must bind to an object or function.
static int requests; // Zero-initialized before dynamic initialization.
thread_local void* cache; // Null pointer before dynamic initialization.
Default-Initialization #
A declaration without an initializer default-initializes its object. A class invokes its selected default constructor; an array default-initializes every element. A scalar with automatic or dynamic storage duration receives no initialization, so it cannot safely be read until assigned.
Widget widget; // Calls Widget::Widget().
int count; // No initial value.
Value-Initialization #
Value-initialization uses an empty initializer in contexts such as T{} or T(). Scalars become zero. Class types select a default constructor; if that constructor is not user-provided, zero-initialization occurs before the constructor body. Arrays value-initialize each element.
int count{};
std::array<int, 4> flags{};Copy, Direct, and List Initialization #
Copy-initialization permits implicit conversions but does not select an explicit constructor. Direct-initialization can select explicit constructors. Direct-list-initialization can also select one, while copy-list-initialization is ill-formed if the selected constructor is explicit.
struct Port {
explicit Port(int);
};
Port a(443); // Direct-initialization.
Port b{443}; // Direct-list-initialization.
// Port c = 443; // Error: explicit constructor is excluded.
List-initialization rejects narrowing conversions. It first gives viable std::initializer_list constructors priority, which can intentionally produce a different result from parentheses.
std::vector<int> repeated(3, 7); // 7, 7, 7
std::vector<int> elements{3, 7}; // 3, 7
// int truncated{3.5}; // Error: narrowing.
Use braces for a narrowing check when the initializer-list preference is not a semantic mismatch. Use parentheses when a type intentionally distinguishes a count/value constructor from a list of values.
Aggregates, Arrays, and Storage Duration #
Arrays are aggregates. Braced aggregate initialization proceeds in declaration order, and omitted elements are initialized according to the applicable empty-initializer rules.
int values[5]{1, 2}; // Remaining elements are zero.
struct Endpoint {
std::string host;
int port;
};
Endpoint local{.host = "localhost", .port = 443}; // C++20 designated initialization.
C++20 also permits parenthesized aggregate initialization. Unlike braced aggregate initialization, it permits narrowing and does not extend the lifetime of a temporary bound to a reference element. C++ designated initializers are more constrained than C’s: they name direct non-static data members in declaration order, and C++ has no array-index designators.
Objects with static or thread storage duration are constant-initialized when the declaration qualifies; otherwise static initialization zero-initializes them. All static initialization completes before dynamic initialization. constinit requires static initialization but does not make an object const; constexpr declares a value usable in constant evaluation when its other requirements are met. These are storage and evaluation guarantees, not replacements for ordinary object initialization.
References and Lifetime #
Reference initialization binds an alias; it does not create a second object. The binding form and value category control whether a temporary’s lifetime is extended.
int value = 7;
int& writable = value;
const int& extended = 42; // The temporary lives as long as `extended`.
Assignment through writable changes value; it does not rebind the reference. Treat initialization as the point at which an object’s invariant begins, and treat assignment as a separately specified state transition.
Comments
Read or comment on GitHub (sign-in required to comment)