← 문제로

13. 초기화 문법의 함정 ({} vs (), initializer_list 우선, MVP, narrowing)

난이도 중
내 리뷰 · C++
해설 · C++

해설 — 초기화 문법의 함정 ({} vs (), initializer_list 우선, MVP, narrowing)

난이도: 중상

요약

중괄호 {} 초기화는 후보 생성자 중 std::initializer_list를 받는 것을 강하게 우선한다. 그래서 같은 인자라도 (){}의 결과가 달라진다. 실행 출력:

a.size=3 a[0]=7      // (a) vector(count=3, value=7)  → 7,7,7
b.size=2 b[0]=3      // (b) initializer_list{3,7}      → 3,7
c.size=3 c[0]=0      // (c) vector(count=3)            → 0,0,0
d.size=1 d[0]=3      // (d) initializer_list{3}        → 3
p=(1,2) q=(0,0)      // (p) 집합체 초기화, (q) 값 초기화
s='xxxxx' len=5      // (s) string(count=5, ch='x')
t len=2 first=5 second=120   // (t) initializer_list<char>{5,'x'} → '\x05','x'

핵심 개념

중괄호 목록으로 초기화할 때 오버로드 해석은 2단계다:

  1. std::initializer_list 생성자만으로 먼저 시도. 목록 원소들이 그 initializer_list의 원소 타입으로 변환 가능하면 그게 선택된다(다른 일반 생성자보다 우선).
  2. 1단계 후보가 없을 때만 일반 생성자들로 다시 해석.

이 우선 규칙 때문에 "개수+값" 생성자가 가려진다.

단계별 내부 동작

  • (a) vector<int> a(3, 7): 소괄호 → initializer_list가 끼지 않는다. vector(size_type count, const T& value) 호출 → {7,7,7}. size 3.
  • (b) vector<int> b{3, 7}: 중괄호 → vector(std::initializer_list<int>{3,7})가 우선 → 원소 3,7. size 2.
  • (c) vector<int> c(3): 소괄호 → vector(count=3) → 기본값 0 세 개. size 3.
  • (d) vector<int> d{3}: 중괄호 → initializer_list<int>{3} 우선 → 원소 3 하나. size 1. ((3)이었다면 0 세 개가 됐을 것 — 한 글자 차이로 의미가 완전히 달라진다.)
  • (p) Point p{1,2}: Point는 사용자 정의 생성자가 없는 집합체(aggregate). 중괄호로 멤버를 순서대로 초기화 → x=1,y=2.
  • (q) Point q{}: 빈 중괄호 → 값 초기화 → 모든 멤버 0 → (0,0). (Point q;였다면 지역 변수라 미초기화 쓰레기값.)
  • (s) string s(5,'x'): 소괄호 → string(count=5, char='x')"xxxxx".
  • (t) string t{5,'x'} ⚠️: 중괄호 → std::stringinitializer_list<char> 생성자가 있다. 5char로 변환되어 '\x05'(값 5), 'x'는 120. 즉 두 글자짜리 문자열 "\x05x". "x가 5개"가 절대 아니다.

흔한 오해·함정

  • most vexing parse (i): Widget w();는 "기본 생성으로 만든 변수 w"가 아니라 인자 없는 함수 w의 선언이다. Widget w{};(또는 Widget w;)로 써야 객체가 만들어진다. std::vector<int> v(std::istream_iterator<int>(cin), std::istream_iterator<int>()) 같은 형태도 함수 선언으로 파싱되는 고전 사례.
  • narrowing 금지 (ii): int n{3.5};double→int가 손실 변환이라 중괄호에서 컴파일 에러(narrowing 금지). 반면 int n(3.5);는 조용히 3으로 잘린다. 중괄호는 의도치 않은 축소를 컴파일 타임에 잡아준다 — 이건 함정이자 안전장치.
  • "{}는 항상 더 안전/현대적이니 무조건 쓰자" → initializer_list 우선 때문에 vector/string 같은 컨테이너에서는 의미가 바뀐다. "개수/크기"를 줄 땐 (), "원소 목록"을 줄 땐 {} 로 의도를 구분해야 한다.

면접 포인트

  • {}initializer_list 생성자를 우선한다는 2단계 해석과, vector{3,7} vs vector(3,7)의 결과 차이.
  • Point q{}(값 초기화, 0) vs Point q;(미초기화)의 차이, 집합체 초기화 규칙.
  • most vexing parse가 무엇이고 어떻게 피하는지, 중괄호의 narrowing 금지가 주는 안전성.