값 초기화(Value initialization)
값 초기화(Value initialization)
빈 초기화식, 예를 들어 T()나 T{}로 객체를 만들면 어떤 일이 일어날까요? 기본 생성자를 그냥 부르는 걸까요? C++는 이 경우 **값 초기화(value initialization)**라는 특별한 초기화를 수행해요. 값 초기화는 빈 초기화식으로 객체를 생성할 때 수행되는 초기화예요.
출처: cppreference
본문
문법
T() |
(1) | |
new T() |
(2) | |
Class::Class(...) : member() { ... } |
(3) | |
T object{}; |
(4) | (C++11부터) |
T{} |
(5) | (C++11부터) |
new T{} |
(6) | (C++11부터) |
Class::Class(...) : member{} { ... } |
(7) | (C++11부터) |
동작 방식(Explanation)
값 초기화는 다음 상황에서 수행돼요.
모든 경우에, 빈 중괄호 {}가 쓰이고 T가 집계 타입(aggregate type)이면, 값 초기화 대신 집계 초기화(aggregate initialization)가 수행돼요.
T가 기본 생성자는 없지만 std::initializer_list를 받는 생성자가 있는 클래스 타입이면, 목록 초기화(list-initialization)가 수행돼요. (C++11부터)
값 초기화의 효과는 다음과 같아요.
T가 (cv-한정일 수 있는) 클래스 타입이면:T에 대한 기본 초기화가 생성자를 선택하고, 그 생성자가 사용자 선언(until C++11)사용자 제공(since C++11) 생성자가 아니면 객체가 먼저 0 초기화(zero-initialized)돼요.- 어쨌든 객체는 기본 초기화(default-initialized)돼요.
- 그렇지 않고
T가 배열 타입이면, 배열의 각 요소가 값 초기화돼요. - 그렇지 않으면 객체는 0 초기화돼요.
정리하면, 값 초기화는 스칼라 타입에는 0을, 클래스 타입에는 조건에 따라 0 초기화 후 기본 초기화를 적용하는 방식이에요.
주의할 점(Notes)
T object(); 문법은 객체를 초기화하지 않아요. 인자를 받지 않고 T를 반환하는 함수를 선언할 뿐이에요. C++11 이전에 이름 있는 변수를 값 초기화하는 방법은 T object = T();였는데, 이는 임시 객체를 값 초기화한 뒤 그 임시 객체를 복사 초기화해요. 대부분의 컴파일러는 이 경우 복사를 최적화해 없애요.
참조는 값 초기화할 수 없어요.
함수 스타일 캐스트에서 설명했듯이, T() (1) 문법은 T가 배열 타입을 가리킬 때 금지되지만 T{} (5)는 허용돼요.
모든 표준 컨테이너(std::vector, std::list 등)는 단일 size_type 인자로 생성되거나 resize() 호출로 커질 때 요소들을 값 초기화해요. 할당기(allocator)가 construct의 동작을 커스터마이즈하지 않는 경우에요.
예제
여기서는 같은 {} 문법이 타입에 따라 어떻게 달라지는지 볼게요. 스칼라는 0으로, 클래스는 기본 생성자 유무에 따라 0 초기화 후 기본 초기화로 처리돼요.
#include <cassert>
#include <iostream>
#include <string>
#include <vector>
struct T1
{
int mem1;
std::string mem2;
virtual void foo() {} // T1이 집계 타입이 아니도록 보장
}; // 암시적 기본 생성자
struct T2
{
int mem1;
std::string mem2;
T2(const T2&) {} // 사용자 제공 복사 생성자
}; // 기본 생성자 없음
struct T3
{
int mem1;
std::string mem2;
T3() {} // 사용자 제공 기본 생성자
};
std::string s{}; // 클래스 => 기본 초기화, 값은 ""
int main()
{
int n{}; // 스칼라 => 0 초기화, 값은 0
assert(n == 0);
double f = double(); // 스칼라 => 0 초기화, 값은 0.0
assert(f == 0.0);
int* a = new int[10](); // 배열 => 각 요소의 값 초기화
assert(a[9] == 0); // 각 요소의 값은 0
T1 t1{}; // 암시적 기본 생성자를 가진 클래스 =>
assert(t1.mem1 == 0); // t1.mem1은 0 초기화, 값은 0
assert(t1.mem2 == ""); // t1.mem2는 기본 초기화, 값은 ""
// T2 t2{}; // 오류: 기본 생성자가 없는 클래스
T3 t3{}; // 사용자 제공 기본 생성자를 가진 클래스 =>
std::cout << t3.mem1; // t3.mem1은 기본 초기화되어 불확정 값
assert(t3.mem2 == ""); // t3.mem2는 기본 초기화, 값은 ""
std::vector<int> v(3); // 각 요소의 값 초기화
assert(v[2] == 0); // 각 요소의 값은 0
std::cout << '\n';
delete[] a;
}
가능한 출력:
42
t3.mem1이 초기화되지 않은 값이므로 이 출력 값은 실행 환경에 따라 달라질 수 있어요.
결함 보고(Defect reports)
이전에 발표된 C++ 표준들에 소급 적용된 동작 변경 결함 보고들이 있어요.
| DR | 적용 대상 | 발표 당시 동작 | 올바른 동작 |
|---|---|---|---|
| CWG 178 | C++98 | 값 초기화가 없었고, 빈 초기화식이 기본 초기화를 호출했음 (단 new T()는 0 초기화도 수행) |
빈 초기화식이 값 초기화를 호출 |
| CWG 543 | C++98 | 사용자 제공 생성자가 없는 클래스 객체의 값 초기화는 각 하위 객체를 값 초기화하는 것과 동등했음 | 전체 객체를 0 초기화한 뒤 기본 생성자를 호출 |
| CWG 1301 | C++11 | 삭제된 기본 생성자를 가진 공용체의 값 초기화가 0 초기화로 이어졌음 | 기본 초기화됨 |
| CWG 1368 | C++98 | 사용자 제공 생성자가 있으면 0 초기화가 건너뛰어졌음 | 사용자 제공 기본 생성자만 0 초기화를 건너 뜀 |
| CWG 1502 | C++11 | 기본 멤버 초기화가 있어도 사용자 제공 기본 생성자가 없는 공용체의 값 초기화는 객체만 0 초기화했음 | 0 초기화 후 기본 초기화를 수행 |
| CWG 1507 | C++98 | 사용자 제공 생성자가 없는 클래스 객체의 값 초기화는 자명한 기본 생성자의 유효성을 검사하지 않았음 | 자명한 기본 생성자의 유효성이 검사됨 |
| CWG 2820 | C++98 | 0 초기화 뒤의 기본 초기화가 비자명 생성자를 요구했음 | 요구하지 않음 |
| CWG 2859 | C++98 | 클래스 객체의 값 초기화가 기본 초기화가 실제로 사용자 제공 생성자를 선택하지 않아도 0 초기화를 포함할 수 있었음 | 이 경우 0 초기화 없음 |
더 알아보기
- 기본 초기화: 생성자 없이 하는 초기화.
- 0 초기화: 값을 0으로 채우는 초기화.
- 집계 초기화:
{}로 집계 타입을 초기화하는 방법. - 목록 초기화:
{}문법으로 하는 초기화 전반.