Ch2.4: Initialization

What Is an Object?

In C++, an object is a region of storage with a type. Every object has a type that determines what values it can hold and what operations are valid. When we declare an object, we reserve memory for it, and when we initialize it, we give it its first value. Initialization ensures that an object starts in a well-defined state instead of containing indeterminate data.

Initialization Styles

C++ provides several styles of initialization. In modern C++, we should prefer uniform initialization with {}, because it is consistent and prevents narrowing conversions.


#include <fast_io.h>
#include <cstddef>
#include <cstdint>
#include <stdfloat>

int main()
{
    using namespace ::fast_io::iomnp;

    // 1. Copy initialization with =
    ::std::size_t a = 42;            // object a initialized to 42
    println("a = ", a);              // prints 42

    // 2. Direct initialization with ()
    ::std::int_least32_t b(100);     // object b initialized to 100
    println("b = ", b);              // prints 100

    // 3. Uniform initialization with {}
    ::std::float64_t pi{3.14159};    // object pi initialized to 3.14159
    println("pi = ", pi);            // prints 3.14159

    // 4. Zero initialization with {}
    ::std::uint_least16_t u{};       // object u initialized to 0
    println("u = ", u);              // prints 0

    // 5. Copy initialization from another object
    ::std::size_t c = a;             // object c initialized with value of a
    println("c = ", c);              // prints 42

    // 6. Multiple objects initialized together
    ::std::size_t m = 1, n = 2, o = 3;
    println("m = ", m, " n = ", n, " o = ", o); // prints 1 2 3

    // 7. Boolean initialization
    bool flag = true;                // object flag initialized to true
    println("flag = ", flag);        // prints 1
    println("flag (boolalpha) = ", boolalpha(flag)); // prints true

    // 8. Typed integer literals with suffixes
    ::std::size_t sz = 30zu;         // literal 30 as ::std::size_t
    println("sz = ", sz);            // prints 30

    unsigned int ui = 42u;           // literal 42 as unsigned int
    println("ui = ", ui);            // prints 42

    long long big = 1000000ll;       // literal as long long
    println("big = ", big);          // prints 1000000

    // 9. Floating-point literals with suffixes
    float f = 3.14f;                 // literal as float
    double d = 3.14;                 // literal as double (default)
    long double ld = 3.14L;          // literal as long double

    println("f = ", f);              // prints 3.14
    println("d = ", d);              // prints 3.14
    println("ld = ", ld);            // prints 3.14
}

Copy vs Direct Initialization

At first glance, copy initialization (=) and direct initialization (()) look similar. Both give an object its initial value, but they are not the same:


#include <fast_io.h>
#include <string>

int main()
{
    using namespace ::fast_io::iomnp;

    // Copy initialization allows implicit conversions
    ::std::string s1 = "Hello";   // const char* converted to std::string
    println("s1 = ", s1);

    // Direct initialization calls constructor directly
    ::std::string s2("World");
    println("s2 = ", s2);

    // Narrowing conversions
    int i1 = 3.5;   // copy initialization, allowed (truncated to 3)
    // int i2(3.5); // direct initialization, error: narrowing not allowed
}

Key takeaway: Copy initialization is more permissive and may perform implicit conversions. Direct initialization is stricter. In modern C++, we should prefer uniform initialization with {}, because it is consistent and prevents narrowing conversions.

Typed Constants with Macros

In addition to suffixes like zu or ll, C++ also provides macros in <cinttypes> to create constants of exact integer types. These macros are useful when you want to guarantee the type of a literal across platforms.


#include <fast_io.h>
#include <cinttypes>
#include <cstdint>

int main()
{
    using namespace ::fast_io::iomnp;

    // Constant of type ::std::uint_least32_t
    ::std::uint_least32_t x = UINT_LEAST32_C(20);
    println("x = ", x); // prints 20

    // Constant of type ::std::int_least64_t
    ::std::int_least64_t y = INT_LEAST64_C(1000000);
    println("y = ", y); // prints 1000000
}

These macros expand to literals with the correct type. For example, UINT_LEAST32_C(20) ensures the constant is of type ::std::uint_least32_t, regardless of the underlying platform.

Comparison of Initialization Styles

Style Syntax Example Notes
Copy initialization = int x = 5; Traditional style, may allow implicit conversions.
Direct initialization () int x(5); Constructor-style, stricter than copy.
Uniform initialization {} int x{5}; Preferred in modern C++, prevents narrowing conversions.
Zero initialization {} int x{}; Initializes to 0 (or equivalent).
Copy from object = other int y = x; Initializes with another object’s value.
Multiple objects int a=1, b=2; int a=1, b=2; Compact declaration of several objects.
Boolean = true bool flag = true; Initializes to true/false.
Typed integer literal zu, u, l, ll auto n = 30zu; Suffixes specify exact integer type.
Floating-point literal f, L float f = 3.14f; Suffixes specify float or long double.
Typed constant macros UINT_LEAST32_C(...) auto x = UINT_LEAST32_C(20); Macros from <cinttypes> guarantee exact integer type across platforms.