중괄호 초기화에는 칭찬할 만한 점이 많습니다. 가장 다양한 문맥에서 사용할 수 있는 구문이며, 암묵적인 좁히기 변환을 방지해주며, C++의 가장 성가신 구문 해석으로부터 자유로우니 삼관왕인 셈입니다. 그렇다면 이번 항목의 제목이 이를테면 "중괄호 초기화 구문을 선호하라"가 아닌 이유는 무엇일까요??
중괄호 초기화의 단점은 종종 예상치 못한 행동을 보인다는 것입니다. 그런 행동은 중괄호 초기치와 std::initializer_list, 그리고 생성자 중복 적재 해소 사이의 괴상하게 뒤얽힌 관계에서 비롯된 것입니다. 그 셋의 상호작용 때문에, 코드가 마땅히 해야 할 일을 안 하고 엉뚱한 일을 하는 것처럼 보이는 현상이 발생합니다. 예를 들어, Chapter(2)에서 설명하듯이, 다른 방식으로(auto를 사용하지 않고) 선언된 변수에 대해서는 std::initilizer_list 형식으로 연역되는 경우가 많습니다. 그래서 auto를 좋아하면 할수록 중괄호 초기화를 점점 멀리하는 경향이 생기게 됩니다.
생성자 호출에서 std:;initializer_list 매개변수가 관여하지 않는 한 괄호와 중괄호의 의미는 같습니다.
<의사 코드>
class Widget
{
public:
Widget(int i, bool b); // std::initializer_list
Widget(int i, double d); // 매개변수를 선언하지 않는 생성자
...
};
Widget w1(10, true); // 첫 생성자를 호출
Widget w2{10, true}; // 역시 첫 생성자를 호출
Widget w3(10, 5.0); 둘째 생성자를 호출
Widget w4{10, 5.0}; 역시 둘째 생성자를 호출
그러나 생성자 중 하나 이상이 std::initializer_list 형식의 매개변수를 선언한다면, 중괄호 초기화 구문은 이상하게도 std::initializer_list를 받는 중복 적재 버전을 강하게 선호합니다. 강하게라고 표현한 점을 주의해야 합니다. 중괄호 초기치가 쓰인 호출을 std::initializer_list를 받는 버전의 생성자 호출로 해석할 여지가 조금이라도 있으면, 컴파일러는 반드시 그 해석을 선택합니다. 예를 들어 위에 나온 Widget 클래스에 std::initialzier_list<long double>을 받는 생성자를 하나 추가해봅시다.
class Widget
{
public:
Widget(int i, bool b); // 이전과 동일
Widget(int i, double d); // 이전과 동일
Widget(std::initializer_list<long double> il); // 추가됨
...
};
이렇게 하면, 다음 코드의 주석에서 보듯이 Widget의 인스턴스 w2와 w4는 새 생성자를 통해서 생성됩니다. std::initializer_list의 요소들의 형식(long double)이 주어진 두 인수 형식 모두에 대해 더 나쁜(비std::initializer_list 생성자들 보다) 부합임에도 이런 일이 발생함을 주목하기 바랍니다.
Widget w1(10, true); // 괄호를 사용한 경우; 이전처럼 첫 생성자를 호출
Widget w2{10, true}; // 중괄호를 사용한 경우; 이번에는 std::initializer_list 생성자 호출 (10과 true가 long double로 변환됨)
Widget w3(10, 5.0); // 괄호를 사용한 경우; 이전처럼 둘째 생성자를 호출한다
Widget w4{10, 5.0}; // 중괄호를 사용한 경우; 이번에는 std::initializer_list 생성자 호출 (10과 5.0이 long double로 변환됨)
보통은 복사 생성이나 이동 생성이 있어났을 상황에서도 std::initializer_list 생성자가 끼어들어서 기회를 가로챕니다.
class Widget
{
public:
Widget(int i, bool b); // 이전과 동일
Widget(int i, double d); // 이전과 동일
Widget(std::initializer_list il); // 이전과 동일
operator float() const; // float로 변환
...
};
Widget w5(w4); // 괄호 사용, 복사 생성자 호출
Widget w6{w4}; // 중괄호 사용, std::initializer_list 생성자 호출 (w4가 float로 변환 되고 그 float이 long double로 변환됨)
Widget w7(std::move(w4)); // 괄호 사용, 이동 생성자 호출
Widget w8{std::move(w4)}; // 중괄호 사용, std::initializer_list 생성자 호출(w6에서와 마찬가지의 변환들이 일어남)
중괄호 초기치를 std::initializer_list를 받는 생성자에 대응시키고자 하는 컴파일러의 결심은 너무나 강해서, 심지어는 std::initializer_list 생성자가 가능한 최선의 부합인 경우에도 그 생성자를 호출할 수 없는 기현상이 생기기도 합니다.
'컴퓨터과학' 카테고리의 다른 글
[Effective C++] (8-1) 0과 NULL보다 nullptr를 선호할 것 (0) | 2020.05.27 |
---|---|
[Effective C++] (7-3) 객체 생성 시 괄호(())와 중괄호({}) 구분하기 (0) | 2020.05.26 |
[Effective C++] (7-1) 객체 생성 시 괄호(())와 중괄호({}) 구분하기 (0) | 2020.05.25 |
[Effective C++] (6-2) auto가 원치 않은 형식으로 연역될 때에는 명시적 형식의 초기치를 사용합니다 (0) | 2020.05.25 |
[Effective C++] (6-1) auto가 원치 않은 형식으로 연역될 때에는 명시적 형식의 초기치를 사용한다 (0) | 2020.05.25 |