C++11 이전에는 객체를 초기화하는 방법이 너무 파편화되어 있었습니다.
어떤 건 ()를 쓰고, 어떤 건 {}를 쓰고, 어떤 건 =를 썼죠.
특히 "가장 성가신 구문 분석(Most Vexing Parse)"이라는 치명적인 문법적 모호함이 있었습니다.
// C++11 이전의 비극
struct Player { Player(int id) {} };
Player p1(1); // 객체 생성
Player p2(); // ⚠️ 주의: 객체 생성이 아니라 '함수 선언'으로 해석됨!
모든 데이터 타입을 중괄호 { } 하나로 초기화하는 방식입니다.
특징
일관성: 일반 변수, 배열, 구조체, 클래스 모두 {}로 통일 가능합니다.
함수 선언 방지: Player p{};라고 쓰면 컴파일러가 이를 절대 함수 선언으로 오해하지 않습니다.
좁게 만들기 방지(Narrowing Conversion): 데이터 손실이 발생하는 암시적 형변환을 컴파일 단계에서 막아줍니다. (ex. float to int)
int a = 3.14; // (경고만 뜨고) a는 3이 됨. 데이터 손실 발생.
int b{ 3.14 }; // ❌ 컴파일 에러! 데이터 손실을 허용하지 않음 (안전함)
여러 개의 동일한 타입 데이터를 묶어서 생성자에 전달할 수 있게 해주는 도구입니다.
특징
가변 인자 처리: {1, 2, 3, 4} 처럼 개수가 정해지지 않은 리스트를 받을 수 있습니다.
컨테이너 지원: std::vector나 std::map에 데이터를 한꺼번에 넣을 수 있는 이유가 바로 이것 때문입니다.
std::vector<int> v = {1, 2, 3, 4, 5}; // 초기화자 리스트 덕분에 가능
가장 중요합니다. 생성자에 std::initializer_list를 받는 오버로딩이 있다면, 중괄호 초기화는 무조건 이 리스트를 우선적으로 선택합니다.
class MyVector {
public:
MyVector(int size, int value) { /* 사이즈만큼 초기화 */ }
MyVector(std::initializer_list<int> list) { /* 리스트로 초기화 */ }
};
MyVector v1(10, 2); // 첫 번째 생성자 호출 (사이즈 10, 값 2)
MyVector v2{10, 2}; // ⚠️ 두 번째 생성자 호출! (데이터 10과 2가 들어있는 리스트)
이 규칙 때문에 std::vector v{10, 20};을 하면 10개짜리 벡터가 아니라, 10과 20이 들어있는 2개짜리 벡터가 만들어집니다.
auto x{ 1 }; 처럼 대입 연산자 없이 쓰면 우리가 원하는 일반 타입이 나옵니다. x{1 ,2}처럼 인자 2개 이상 시 오류
auto x = { 1 }; 처럼 대입 연산자와 함께 쓰면 std::initializer_list가 나옵니다 (같은 타입)