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:
- Copy initialization uses
=. It may allow implicit conversions and can call a converting constructor. - Direct initialization uses
(). It is stricter and prefers constructors that exactly match the arguments.
#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. |