Uniform Initialization

·2024년 12월 5일
0

C++

목록 보기
18/26

객체 생성 시 괄호와 중괄호를 구분하라

uniform initialization

  • C++은 초기화 할 때 =, (), {}를 지원함
  • 그 중에서 어디서나 사용할 수 있는 것이 중괄호 구문임
  • (장점) =, ()와 달리 암묵적 좁히기 변환(narrowing conversion)을 방지해 줌
  • (단점) 종종 예상치 못한 행동을 보임 (예) std::initializer_list 선호 현상

narrowing conversion이란?

  • 더 큰 범위 또는 더 넓은 정밀도를 가진 데이터 타입을 더 작은 범위 또는 더 낮은 정밀도를 가진 데이터 타입으로 변환하는 것
  • 이 과정에서 데이터가 손실되거나 값이 달라질 수 있으므로 위험한 변환임
// 실수에서 정수로 변환하는 경우
double d = 3.14;
int i = d; // i는 3이됨

// uniform initialization은 암묵적 좁히기 변환을 방지함
int x = 3.14; // 허용 x는 3
int y(3.14);  // 허용 y는 3
int z{3.14};  // 컴파일 오류

uniform initialization의 예기치 못한 동작

class Widget
{
public:
	Widget(int i, bool b);   // (1)
    Widget(int i, double d); // (2)
    // ...
};

Widget w1(10, true);  // (1) 호출
Widget w2{10, true};  // (1) 호출
Widget w3(10, 5.0);   // (2) 호출
Widget w4{10, 5.0};   // (2) 호출

여기 까지는 예상대로 동작함
그러나 생성자 중 하나 이상이 std::initializer_list 형식의 매개변수를 선언하면 초기화 구문은 이 버전을 강하게 선호함

class Widget
{
public:
	Widget(int i, bool b);   						// (1)
    Widget(int i, double d); 						// (2)
    Widget(std::initializer_list<long double> il);  // (3)
};

Widget w1(10, true);  // (1) 호출
Widget w2{10, true};  // (3) 호출 10과 true가 long double로 변환됨
Widget w3(10, 5.0);   // (2) 호출
Widget w4{10, 5.0};   // (3) 호출 10과 5.0이 long double로 변환됨

생성자 중 정확히 형식이 일치하는 경우가 있음에도 불구하고 std::initializer_list<bool>
을 호출하려고 함
bool로 변환하기 위해서는 좁히기 변환이 필요하지만 uniform initialization은 이를 방지하므로 컴파일 에러가 발생함 (정확히 형식이 일치하는 생성자가 있음에도 불구하고.. 🤔)

class Widget
{
public:
	Widget(int i, bool b);   						// (1)
    Widget(int i, double d); 						// (2)
    Widget(std::initializer_list<bool> il);  		// (3)
};

Widget w4{10, 5.0};   // 컴파일 에러

우선 순위를 가지는 정도가 아니라 다른 생성자가 고려 대상에서 제외될 정도로 가려버림

이 문제에 직접 영향을 받는 클래스 중 하나가 vector
vector비std::initializer_list 생성자와 std::initializer_list 생성자 둘 다 있기 때문

vector<int> v1(1, 2); // 비 std::initializer_list 생성자 사용
					  // 모든 요소의 값이 2인 요소 1개짜리 vector가 생성됨
                      
vector<int> v2{1, 2}; // std::initializer_list 생성자 사용
					  // 값이 1, 2인 두 요소를 담은 vector가 생성됨

=> 클래스를 작성할 때 생성자 중에 std::initializer_list를 받는 함수가 하나 이상 존재한다면 중괄호 초기화 사용을 주의해야 함을 기억하자

추가 학습

std::initializer_list란?

  • C++11에서 도입된 클래스 템플릿
  • {}를 이용해서 생성자를 호출할 때, 클래스의 생성자들 중에 std::initializer_list를 인자로 받는 생성자가 있다면 전달됨
  • 컨테이너와 유사한 동작이 가능 (내부적으로 begin(), end() 제공함)
  • 불변 데이터 구조를 가짐 (값을 복사하여 저장하므로 생성된 이후에는 변경 불가)
  • 가변 개수의 인자 처리
initializer_list<int> il = { 1, 2, 3 };
il.begin()[0] = 10; // 컴파일 오류

vector를 사용할 때 uniform initialization을 사용하는 경우 std::initializer_list 생성자가 호출되는데 왜 값 변경이 가능할까?

초기화는 std::initializer_list 생성자로 호출되지만 값을 복사하여 vector에 데이터를 저장하기 때문에 생성된 이후 initializer_list와 상관없이 값 변경이 가능함

=> std::initializer_list는 불변이지만 이를 사용해 초기화된 컨테이너는 컨테이너의 특성에 따라 값을 변경할 수 있음

0개의 댓글

관련 채용 정보