C++ Value Initialization | Empty {} and ()
이 글의 핵심
Value initialization uses empty () or {}. Scalars become zero-like; classes call the default constructor. Differs from default initialization for locals; compares with zero initialization.
The Initialization Problem
C++ has multiple initialization rules, and the wrong one leads to undefined behavior from reading indeterminate values. Understanding when you get a guaranteed zero vs garbage is essential.
int a; // default initialization — indeterminate value (garbage)!
int b{}; // value initialization — guaranteed 0
int c = 0; // copy initialization — 0
// Reading a is undefined behavior if it was never assigned
std::cout << a; // could print anything, crash, or worse
std::cout << b; // always prints 0
Value initialization is the mechanism that gives you a safe, predictable starting value.
When Value Initialization Happens
Value initialization is triggered by empty parentheses or empty braces:
// Variable declaration with empty braces
int x{}; // value init → 0
double d{}; // value init → 0.0
int* ptr{}; // value init → nullptr
bool flag{}; // value init → false
// Temporary (prvalue) with empty parens or braces
int temp = int(); // value init → 0
int temp2 = int{}; // value init → 0 (same result)
// new expression with empty parens
int* heap = new int(); // value init → 0
int* heap2 = new int{}; // value init → 0
// Arrays
int arr[5]{}; // all elements value-initialized → all zero
int* dynArr = new int[5](); // all zero
// Class member with default member initializer using {}
class Counters {
int hits{}; // value-initialized when Counters is constructed
int misses{}; // value-initialized
double ratio{}; // 0.0
public:
Counters() = default; // uses the member initializers above
};
Value Initialization Rules by Type
The behavior depends on the type:
| Type | Value initialization result |
|---|---|
| Scalar (int, double, pointer, bool) | Zero (0, 0.0, nullptr, false) |
| Array | Each element is value-initialized |
| Class with user-provided constructor | Default constructor is called |
| Aggregate (no user constructor) | Zero-initialized, then default constructor if any |
| Class with no user-provided constructor | Zero-initialized first |
// Scalar
int n{}; // 0
double d{}; // 0.0
char* p{}; // nullptr
bool b{}; // false
// Aggregate
struct Point { int x, y; };
Point pt{}; // x=0, y=0 — both zero-initialized
// Class with constructor
class Widget {
int id_;
std::string name_;
public:
Widget() : id_(0), name_("default") {}
};
Widget w{}; // default constructor called: id=0, name="default"
// Array
int arr[4]{}; // {0, 0, 0, 0}
Default Initialization vs Value Initialization
This is the most practically important distinction:
// Local variables — default initialization
int local; // indeterminate — do NOT read without assigning
double ratio; // indeterminate
int* ptr; // indeterminate (not nullptr!)
// Local variables — value initialization
int safeLocal{}; // 0
double safeRatio{}; // 0.0
int* safePtr{}; // nullptr
// Static/global variables — always default-initialized to zero
static int count; // 0 — static storage is zero-initialized
int globalCount; // 0 — same
The rule to remember: for local scalar variables, T x; is indeterminate; T x{}; is zero.
void process() {
int counter; // WARNING: indeterminate
counter++; // undefined behavior — reading uninitialized
int safeCounter{}; // 0
safeCounter++; // OK — well-defined: 1
}
{} vs () for Value Initialization
Both T{} and T() trigger value initialization, but they differ in two ways:
Narrowing Conversions
double pi = 3.14159;
int a(pi); // OK — narrowing truncates: a = 3 (compiles with warning)
int b{pi}; // Error — narrowing conversion rejected at compile time
{} catches narrowing at compile time, which prevents accidental data loss.
Most Vexing Parse
struct Widget { Widget() {} };
Widget w1(); // PROBLEM: this is a function declaration, not a variable!
// "w1 is a function that takes no args and returns Widget"
Widget w2{}; // Correct: value-initialized Widget object
Widget w3; // Also OK: default-initialized Widget object (same here, constructor called)
The Most Vexing Parse is a notorious C++ ambiguity. Using {} eliminates it.
initializer_list Ambiguity
std::vector<int> v1(5, 0); // 5 elements, all zero: {0,0,0,0,0}
std::vector<int> v2{5, 0}; // 2 elements: {5, 0}
When a class has an initializer_list constructor, {} prefers it. For std::vector, {5, 0} means a vector with elements 5 and 0, not 5 zeros. Use () when you want the non-initializer_list constructor.
new T() vs new T
For heap allocations, the distinction between value and default initialization matters:
// Default initialization — value is indeterminate for scalars
int* p1 = new int; // *p1 is indeterminate
int* p2 = new int[5]; // all 5 elements indeterminate
// Value initialization — scalars become zero
int* p3 = new int(); // *p3 == 0
int* p4 = new int{}; // *p4 == 0 (same)
int* p5 = new int[5](); // all zero
int* p6 = new int[5]{}; // all zero
// For class types with user constructors — same either way
Widget* w1 = new Widget; // default constructor called
Widget* w2 = new Widget(); // default constructor called
Use new T() or new T{} consistently when you want zero-initialized memory, even if you’ll overwrite it immediately — it avoids accidentally reading indeterminate values.
In modern C++, prefer smart pointers:
auto p = std::make_unique<int>(); // *p == 0
auto arr = std::make_unique<int[]>(5); // all 5 elements zero
Value Initialization in Containers
When standard containers create new elements, they value-initialize them:
#include <vector>
#include <iostream>
int main() {
// resize adds value-initialized elements
std::vector<int> v;
v.resize(5);
for (int x : v) std::cout << x << ' '; // 0 0 0 0 0
std::cout << '\n';
// Constructor with count — also value-initializes
std::vector<double> d(3); // {0.0, 0.0, 0.0}
// insert/emplace don't value-initialize — you provide the value
v.push_back(42); // adds 42
}
Member Default Member Initializers
C++11 lets you provide default values for members at the declaration site. These are used during value initialization:
class Connection {
int fd_ = -1; // default member initializer
bool connected_ = false;
std::string host_; // default-constructed (empty string)
int retries_ = 3;
public:
Connection() = default; // uses all default member initializers
explicit Connection(std::string host, int fd)
: fd_(fd), host_(std::move(host)), connected_(true) {}
};
Connection c1; // fd=-1, connected=false, host="", retries=3
Connection c2("db", 5); // fd=5, connected=true, host="db", retries=3
Default member initializers with {} (or = value) are the modern way to ensure members are always initialized to a known state.
Comparison Table
| Form | Local int | Local class | Comment |
|---|---|---|---|
int x; | Indeterminate | Default ctor | Unsafe for scalars |
int x{}; | 0 | Default ctor | Safe, preferred |
int x = 0; | 0 | Copy init | Clear intent for scalars |
int x(0); | 0 | Matching ctor | OK but avoid MVP with no args |
new int; | Indeterminate | Default ctor | Unsafe for scalars |
new int(); | 0 | Default ctor | Safe |
Key Takeaways
T x{};triggers value initialization — scalars become zero, classes call default constructorT x;(no initializer) for a local scalar is default initialization — the value is indeterminate (undefined behavior if read)- Static and global variables are always zero-initialized regardless of how they’re declared
{}prevents narrowing conversions at compile time and avoids the Most Vexing Parse — prefer it over()for local variablesvector::resize,new T(), and container constructors with a count all value-initialize elements- Default member initializers (
int x = 0;orint x{};in class body) ensure members are always initialized even with the default constructor
자주 묻는 질문 (FAQ)
Q. 이 내용을 실무에서 언제 쓰나요?
A. Value initialization uses empty () or {}. Scalars become zero-like; classes call the default constructor. Differs from d… 실무에서는 위 본문의 예제와 선택 가이드를 참고해 적용하면 됩니다.
Q. 선행으로 읽으면 좋은 글은?
A. 각 글 하단의 이전 글 또는 관련 글 링크를 따라가면 순서대로 배울 수 있습니다. C++ 시리즈 목차에서 전체 흐름을 확인할 수 있습니다.
Q. 더 깊이 공부하려면?
A. cppreference와 해당 라이브러리 공식 문서를 참고하세요. 글 말미의 참고 자료 링크도 활용하면 좋습니다.
같이 보면 좋은 글 (내부 링크)
이 주제와 연결되는 다른 글입니다.
- [C++ Copy Initialization: The = Form, explicit, and Copy](/en/blog/cpp-copy-initialization/
- [C++ Designated Initializers (C++20)](/en/blog/cpp-designated-initializers/
- [C++ Functions: Parameters, Return Values, Overloading, and](/en/blog/cpp-function-basics/
이 글에서 다루는 키워드 (관련 검색어)
C++, value initialization, zero initialization, default initialization, C++11 등으로 검색하시면 이 글이 도움이 됩니다.