C++ Value Initialization | "값 초기화" 가이드
이 글의 핵심
값 초기화는 빈 () 또는 {}로 객체를 만들 때 적용되는 규칙입니다. 기본 타입은 0·nullptr, 클래스는 기본 생성자입니다. 기본 초기화와 달리 지역 `int`도 안전한 기본값을 만들 수 있고, `{}`와 `()`의 미묘한 차이(좁히기, 파싱)를 알아두면 실수를 줄일 수 있습니다.
값 초기화란?
값 초기화(Value Initialization) 는 빈 괄호 {} 또는 ()로 변수를 초기화하는 방법입니다. 기본 타입은 0으로 초기화되고, 클래스 타입은 기본 생성자가 호출됩니다.
int x{}; // 0
int* ptr{}; // nullptr
double d{}; // 0.0
왜 필요한가?: 기본 초기화는 쓰레기 값을 남길 수 있지만, 값 초기화는 안전한 기본값을 보장합니다.
// 기본 초기화: 쓰레기 값
int x; // 예: 0x7fff5fbff5e0 (쓰레기)
// 값 초기화: 0
int y{}; // 0
다른 초기화와의 관계: 언제 “값 초기화”인가?
값 초기화는 문맥에 따라 다음처럼 나타납니다.
- 빈 중괄호
T{},T{ }— 리스트 초기화 문법이지만, 인자가 없으면 객체에 대해 값 초기화 규칙으로 이어집니다(리스트 초기화 참고). - 빈 괄호
T()— 임시 객체를 만들 때 값 초기화입니다.new T()도 마찬가지입니다. T t;— 초기화자가 없으면 기본 초기화이지, 값 초기화가 아닙니다(지역 기본 타입은 쓰레기 가능).
즉, “0을 보장하고 싶다”면 기본 초기화가 아니라 값 초기화(또는 0 초기화가 포함된 경로) 를 선택해야 합니다.
{} vs (): 둘 다 값 초기화일 때
많은 경우 int()와 int{}는 같은 값 초기화 결과(정수 0)를 줍니다. 차이는 문법적 함정과 리스트 초기화 규칙에 있습니다.
| 항목 | () | {} |
|---|---|---|
값 초기화 (예: int) | int() → 0 | int{} → 0 |
| 좁히기(narrowing) | int(3.14)는 허용(잘림) | int{3.14}는 일반적으로 컴파일 거부 |
| 가장 성가신 파싱 | T f();는 함수 선언일 수 있음 | T f{};는 객체 한 개 |
std::vector | vector<int>(10, 0)는 크기·값 | vector<int>{10, 0}는 요소 두 개 |
int a = int(); // 값 초기화, 0
int b{}; // 값 초기화, 0
// int c(3.14); // 좁히기 허용 → 3
// int d{3.14}; // 에러: narrowing
// 가장 성가신 파싱 회피
Widget w1; // 기본 초기화(Widget이 어떻게 정의됐는지에 따름)
Widget w2{}; // 값 초기화(기본 생성자)
// Widget w3(); // Widget w3() — 함수 선언!
auto p1 = new double; // 기본 초기화(쓰레기 가능)
auto p2 = new double(); // 값 초기화 → 0.0
auto p3 = new double{}; // 값 초기화 → 0.0
실무 팁: “기본값 0·빈 객체”가 목적이면 지역 변수와 멤버에는 **{}**를 우선 고려하면, 기본 초기화로 인한 쓰레기 값을 줄이면서도 좁히기 실수를 컴파일 단계에서 잡을 수 있습니다.
클래스 vs 기본 타입에서의 값 초기화
- 기본 타입 (
int,char, 포인터 등): 값 초기화의 결과는 0 또는 nullptr 등으로 정해진 값입니다. - 클래스 타입
T: 기본 생성자가 호출됩니다. 이때 기본 생성자가 없거나 접근 불가하면 컴파일 오류입니다. - 집합체(aggregate) 인 클래스: 값 초기화
T{}는 종종 0 초기화 후 집합체 규칙과 맞물립니다(집합체 초기화, 0 초기화 참고).
struct Pod { int x; int y; };
Pod p{}; // x, y 모두 0에 가깝게(값/집합 규칙에 따라)
class NonDefault {
NonDefault(int) {}
};
// NonDefault nd{}; // 기본 생성자 없음 → 오류
기본 타입
int x{}; // 0
double d{}; // 0.0
bool b{}; // false
char c{}; // '\0'
int* ptr{}; // nullptr
실전 예시
예시 1: 배열 초기화
int arr1[5]; // 쓰레기 값
int arr2[5]{}; // 모두 0
struct Point {
int x, y;
};
Point points[3]{}; // 모두 {0, 0}
예시 2: 클래스
class Widget {
int value;
public:
Widget() : value{} {} // value = 0
};
int main() {
Widget w{}; // 기본 생성자 호출
}
예시 3: new 연산자
// 기본 초기화
int* p1 = new int; // 쓰레기 값
// 값 초기화
int* p2 = new int(); // 0
int* p3 = new int{}; // 0
// 배열
int* arr1 = new int[5]; // 쓰레기 값
int* arr2 = new int[5](); // 모두 0
int* arr3 = new int[5]{}; // 모두 0
예시 4: 멤버 초기화
class Data {
int x{}; // 0
double y{}; // 0.0
int* ptr{}; // nullptr
public:
Data() = default;
};
int main() {
Data d;
// d.x = 0, d.y = 0.0, d.ptr = nullptr
}
초기화 비교
// 기본 초기화
int x; // 쓰레기 값
// 값 초기화
int y{}; // 0
int z = int(); // 0
// 직접 초기화
int w(10); // 10
초기화 방법 정리:
| 초기화 방법 | 문법 | 결과 (기본 타입) | 결과 (클래스) |
|---|---|---|---|
| 기본 초기화 | int x; | 쓰레기 값 | 기본 생성자 |
| 값 초기화 | int x{}; | 0 | 기본 생성자 |
| 직접 초기화 | int x(10); | 10 | 생성자 호출 |
| 복사 초기화 | int x = 10; | 10 | 복사 생성자 |
실무 권장:
- 안전성 우선:
{}사용 (값 초기화) - 명시적 값:
= value또는(value)사용
// ✅ 안전한 패턴
int counter{}; // 0으로 시작
int* ptr{}; // nullptr로 시작
// ✅ 명시적 값
int max_size = 100;
std::string name("Alice");
자주 발생하는 문제
문제 1: 쓰레기 값
// ❌ 초기화 안함
int x;
std::cout << x << std::endl; // 쓰레기 값
// ✅ 값 초기화
int x{};
std::cout << x << std::endl; // 0
문제 2: 배열
// ❌ 일부만 초기화
int arr[5] = {1, 2}; // {1, 2, 0, 0, 0}
// ✅ 모두 0
int arr[5]{}; // {0, 0, 0, 0, 0}
문제 3: 포인터
// ❌ 초기화 안함
int* ptr;
if (ptr) {} // 정의되지 않은 동작
// ✅ nullptr
int* ptr{};
if (ptr) {} // OK
문제 4: 클래스 멤버
// ❌ 초기화 안함
class Bad {
int value; // 쓰레기 값
};
// ✅ 값 초기화
class Good {
int value{}; // 0
};
성능 고려사항
// 큰 배열
int arr[1000000]{}; // 모두 0으로 초기화 (시간 소요)
// 필요한 경우만 초기화
int arr[1000000];
for (int i = 0; i < 1000000; i++) {
arr[i] = compute(i);
}
성능 분석:
#include <chrono>
#include <iostream>
using namespace std::chrono;
// 값 초기화 (모두 0)
auto start = steady_clock::now();
int arr1[10000000]{};
auto elapsed1 = steady_clock::now() - start;
// 기본 초기화 (쓰레기 값)
start = steady_clock::now();
int arr2[10000000];
auto elapsed2 = steady_clock::now() - start;
std::cout << "값 초기화: " << duration_cast<milliseconds>(elapsed1).count() << "ms\n";
std::cout << "기본 초기화: " << duration_cast<milliseconds>(elapsed2).count() << "ms\n";
// 값 초기화가 더 느림 (0으로 채우는 비용)
실무 권장:
- 작은 배열/변수: 항상 값 초기화 (안전성)
- 큰 배열: 즉시 덮어쓸 예정이면 기본 초기화 고려
- 의심스러우면: 값 초기화 (안전성 > 성능)
// ✅ 작은 변수: 항상 초기화
int counter{};
double sum{};
// ✅ 큰 배열: 즉시 덮어쓰기
int buffer[1000000];
read_from_file(buffer, sizeof(buffer)); // 즉시 덮어씀
// ✅ 큰 배열: 부분 사용
int cache[1000000]{}; // 0으로 초기화 (안전)
실무 패턴
패턴 1: 안전한 카운터
class Counter {
int count_{}; // 0으로 초기화
public:
void increment() { ++count_; }
int get() const { return count_; }
};
패턴 2: 옵셔널 포인터
class Resource {
int* data_{}; // nullptr로 초기화
public:
void allocate(int size) {
data_ = new int[size]{}; // 배열도 0으로 초기화
}
~Resource() {
delete[] data_; // nullptr 삭제는 안전
}
};
패턴 3: 통계 구조체
struct Statistics {
int count{};
double sum{};
double min{};
double max{};
void add(double value) {
if (count == 0) {
min = max = value;
} else {
if (value < min) min = value;
if (value > max) max = value;
}
sum += value;
++count;
}
double average() const {
return count > 0 ? sum / count : 0.0;
}
};
FAQ
Q1: 값 초기화는 무엇인가요?
A: {} 또는 ()로 초기화하는 방법입니다. 기본 타입은 0, 포인터는 nullptr, 클래스는 기본 생성자가 호출됩니다.
Q2: 기본 초기화와 차이는?
A:
- 기본 초기화 (
int x;): 쓰레기 값 가능 - 값 초기화 (
int x{};): 0 또는 기본값
Q3: 성능 영향은?
A: 0 초기화 비용이 있습니다. 큰 배열은 주의하되, 안전성이 더 중요하면 값 초기화를 사용하세요.
Q4: 언제 사용해야 하나요?
A:
- 안전한 초기화가 필요할 때
- 0 또는 기본값으로 시작해야 할 때
- 쓰레기 값을 방지하고 싶을 때
Q5: 클래스는 어떻게 되나요?
A: 기본 생성자가 호출됩니다. 기본 생성자가 없으면 컴파일 에러입니다.
class Widget {
public:
Widget() { std::cout << "Default constructor\n"; }
};
Widget w{}; // "Default constructor" 출력
Q6: {} vs () 차이는?
A: 대부분 동일하지만, {}는 좁히기 변환(narrowing conversion) 을 방지합니다.
int x(3.14); // OK: 3 (소수점 버려짐)
int y{3.14}; // 에러: narrowing conversion
Q7: 값 초기화 학습 리소스는?
A:
- “C++ Primer” by Lippman, Lajoie, Moo
- “Effective C++” by Scott Meyers
- cppreference.com - Value initialization
관련 글: Zero Initialization, Default Initialization, List Initialization.
한 줄 요약: 값 초기화는 {}로 변수를 안전하게 0 또는 기본값으로 초기화하는 방법입니다.
같이 보면 좋은 글 (내부 링크)
이 주제와 연결되는 다른 글입니다.
- C++ Zero Initialization | “0 초기화” 가이드
- C++ Default Initialization | “기본 초기화” 가이드
- C++ List Initialization | “리스트 초기화” 가이드
관련 글
- C++ Aggregate Initialization |
- C++ Aggregate Initialization 완벽 가이드 | 집합 초기화
- C++ call_once |
- C++ Copy Initialization |
- C++ default와 delete |