'initializer_list'에 해당되는 글 1건

  1. 2021.07.30 [C++11/C++17] 유니폼 초기화 (Uniform Initialization)
반응형

C++11에 유니폼 초기화가 추가되었다.

{}를 이용한 초기화여서 Brace-Initialization 이라고도 부르는것 같다.

 

C++11에 {}를 이용해서 초기화 문법을 따로 추가한 이유는 무엇일까??

사실 이전부터 일부 상황에서는 {}를 이용한 초기화를 사용하고 있었고

일부 상황에서는 ()를 이용한 초기화를 사용하고 있었다.

 

구조체와 배열의 경우에는 {}를 이용해서 초기화를 하고 있었고

하지만 클래스는 ()를 이용해서 생성자를 호출해서 초기화 하는식으로 처리가 되고 있었다.

struct A
{
  int x, y;
};

class B
{
  public:
    B(int x, int y) : mX(x), mY(y) {}
  private:
    int mX, mY;
};


A a = {10, 20};			// {}
B b(10, 20);			// ()

int c[4] = {1, 2, 3, 4};	// {}

C++11에서는 이 부분을 모두 {}를 이용하는 유니폼 초기화로 문법을 통일시켰다고 보면 될 것 같다.

=을 붙이거나 붙이지 않거나 모두 유니폼 초기화를 사용할 수 있다.

A a1 = {10, 20};
B b1 = {10, 20};

A a2{10, 20};
B b2{10, 20};

유니폼 초기화는 일반 자료형에도 사용 가능하다. 아래 예제에서 {}로 초기화할 경우 0이 들어간다.

int a = 1;
int b(1);

// uniform-initialization
int c = {1};	
int d{1};
int e{};	// 0

기왕 유니폼 초기화 기능을 넣고 문법을 통일하면서 한가지 기능을 더 추가하였는데

바로 축소 변환 방지(narrowing) 기능이다.

이는 암묵적 캐스팅이 진행될 때 기존 데이터의 손실이 발생할 경우 에러를 처리해주는 기능을 말한다.

예를 들자면 int에 double 값을 넣을 경우에 소수부분은 손실되게 되는데 이러한 상황을 막는 것이다.

void PrintInt(int x)
{
  std::cout << "Num : " << x << std::endl;
}

A i { 3.14, 3.14 };	// warning
B j { 3.14, 3.14 };	// error
int k { 3.14 };		// error

PrintInt(3.14);		// warning
PrintInt({ 3.14 });	// error

내가 테스트한 환경에서는 구조체의 경우에는 warning 처리로 끝나기는 했고 나머지의 경우에는 모두 축소 변환 에러가 발생했다. (VS 2019 - C++17)

 

클래스 멤버 변수를 선언하면서 바로 초기화 하거나 초기화 리스트에서 배열을 초기화 할때도 사용할 수 있다.


다음은 직접 리스트 초기화와 복제 리스트 초기화에 대한 부분을 간단히 정리하고 넘어가려고 한다.

위에 예제를 보면 a = {}의 형식이거나 아니면 a {} 형식으로 초기화를 하였다.

 

a = {}와 같은 형태를 복제 리스트 초기화라고 하고

a {}와 같은 형태를 직접 리스트 초기화라고 한다.

 

그냥 생성자 호출과 복사 생성자 호출 (= 연산자) 의 느낌이라고 대충 생각해도 될 것 같다.

 

여기서 이야기할 내용은 {}를 auto로 받았을 경우에 타입이 무엇이냐에 대한 것이다.

{}를 auto로 받으면 initializer_list<>으로 변경되고 처리가 되는데 C++17 이전과 이후에 약간 변화가 있어서 정리하려고 한다.

 

C++17 이전은 모두 initializer_list<>로 처리가 되고 C++17은 복제 리스트 초기화만 initializer_list<>로 처리된다.

// before C++ 17
auto a1 = { 1 };	// initializer_list<int>
auto a2 = { 1, 2 };	// initializer_list<int>
auto a3{ 1 };		// initializer_list<int>
auto a4{ 1, 2 };	// initializer_list<int>
auto a5 = { 1, 1.1 };	// error

// after C++ 17
auto a1 = { 1 };	// initializer_list<int>
auto a2 = { 1, 2 };	// initializer_list<int>
auto a3{ 1 };		// int
auto a4{ 1, 2 };	// error
auto a5 = { 1, 1.1 };	// error

C++17 이후 a4의 경우에는 초기화 리스트가 아닌 타입을 추론하게 되는데

이때 하나의 값에 의한 추론만 진행하기 때문에 값이 여러개라고 에러가 발생하게 된다.

그리고 a5의 경우에는 복제 초기화 리스트라도 다른 타입의 값을 넘긴다면 추론에 실패한다.

 

참고로 내가 테스트한 환경에서는 C++14로 낮춰도 a4 부분이 에러가 발생하기는 했다. (VS2019 - C++14)

 

마지막으로 어떤 타입인지 직접 출력해보고 싶으신 분들은

typeinfo의 typeid를 이용해서 콘솔로 출력해보면 좋을 것 같다.

반응형
Posted by msparkms
,