본문 바로가기
NC University/Advanced C++

5일차 - initializer_list

by 날쑤 2015. 8. 5.

Intro

  initializer_list는 동일 타입의 요소를 여러 개 보관하는 타입(정확히는 템플릿)이다. 언뜻 생각해보면 이는 vector와 유사하며, interface도 큰 차이를 보이지 않는다. 차이점은 차후에 살펴보기로 하고, 우선 아래 코드를 통해 int 타입을 저장하는 initializer_list를 생성하고 사용하는 것을 살펴보자.

initializer_list와 함수인자

  기존 C++(C++ 98)에서는 가변 갯수의 같은 타입의 인자를 함수로 전달하고 싶을 경우에는 가변인자 함수를 사용했다. 이는 호출자 쪽에서 전달되는 인자의 개수를 첫번째 인자로 전달해줘야 하는 번거로움이 있다. 또한, 가변인자 함수의 메커니즘은 전혀 안전하지 못하고, 저수준이며, C++의 객체모델과도 잘 맞지 않는다고 Modern C++ Design 책 5장에 나와있다. 하지만 C++ 11부터는 initializer_list를 이용해서 간단하고, C++에 부합하는 형태의 가변인자 함수를 만들 수 있다.

initializer_list와 생성자

  initializer_list는 생성자의 인자로 가장 널리 사용된다. 아래의 코드에서 볼 수 있듯이 brace를 이용해서 객체를 초기화하는 경우에만 initializer_list를 파라메터로 취하는 생성자가 선택된다. 또한 이 생성자는 다른 생성자들보다 우선 순위가 더 높기때문에 적용이 가능한 상황이라면 더 부합하는 생성자가 있더라도 컴파일러는 이 생성자(initializer_list 파라메터를 가지는)를 우선적으로 선택한다.

생성자와 관련된 이야기 조금 더

  최근에 나온 Effective Modern C++에는 이 initializer_list를 인자로 받는 생성자에 대한 이야기를 하나의 item(Item 7)을 할당해서 다루고 있다. 여기에 나와 있는 관련 예제를 조금 소개하고자 한다.

  분명히 10과 true을 인자로 Widget을 생성할 경우에는 (1)이 훨씬 더 적합한 생성자이지만, 어쨌든 변환이 가능하기 때문에 컴파일러는 (2)를 선택한다. 심지어 이는 복사 생성자나 이동 생성자보다도 우선적으로 선택될 수도 있다.

  첫번째와 세번째는 괄호를 사용해서 객체를 초기화하기 때문에 복사 생성자와 이동 생성자가 각각 호출된다. 반면에 두번째와 네번째는 brace를 사용해서 객체를 초기화한다. 이때 컴파일러는 우선 인자를 long double로 변환할 수 있는지를 확인하는데, 추가된 operator float()로 인해 Widget 객체를 float로 변환이 가능하기 때문에 목적하는 변환도 가능함을 알게된다. 그래서 이 둘은 복사 생성자(이동 생성자)가 아닌 initializer_list를 인자로 받는 생성자를 호출한다.

  initializer_list를 인자로 받는 생성자의 무서움(?)은 여기서 그치지 않는다. 더 나아가 변환이 가능해보이기만 하면 컴파일이 되지 않더라도 컴파일러는 이 생성자를 택한다.

  얼핏 보면 위의 코드에서 컴파일 에러가 발생한다는 것이 쉽게 이해가 되지 않을 수 있다. 왜냐하면 인자로 받은 값들의 타입과 정확히 매치되는 생성자 (1)이 존재하기 때문이다. 하지만 컴파일러는 인자들의 타입과 initializer_list의 타입인자간의 변환 가능성(int, double --> bool)만을 보고 (2)를 택하게 된다. 그러나 이전 글에서 언급했던 uniform initialization은 narrowing conversion을 허용하기 않기 때문에 컴파일 에러가 발생한다. 이 에러를 없애려면 (2)를 지우거나 객체 생성 자체를 괄호를 사용해서 해야한다.

  그럼 brace를 사용해서 객체를 초기화할 때 initializer_list를 인자로 받는 생성자가 호출되지 않는 경우는 존재하지 않을까? 그렇지는 않다. 우선 인자들의 타입이 initializer_list의 타입인자로 변환 자체가 불가능한 경우에는 일반 오버로딩 룰을 따른다. 예를 들면, 위의 코드에서 initializer_list의 타입인자가 bool이 아닌 std::string이었다면 int와 double은 string으로 변환이 안되니깐 w7은 (1)을 호출한다. 그리고 이와는 별개로 default 생성자가 있는 상황에서 빈 brace로 객체를 생성하면, 이때는 default 생성자가 호출된다.