3 분 소요

1. 유니폼 초기화(Uniform Initialization)

기존 초기화식의 모호성을 회피하기 위해 생긴 문법

예시코드

#include <iostream>
using namespace std;

class A
{
public:
	A() { std::cout << "A의 생성자 호출!" << std::endl; }
};

int main()
{
	A a();

	return 0;
}

결과


아무것도 출력되지 않음

왜 아무것도 출력되지 않을까?

() 기호가 함수의 인자를 정의되는데 사용되는데 C++의 컴파일러는 함수의 정의처럼 보이면 전부 함수라고 인식하기 때문
즉, 위의 A a()구문은 return 값으로 A를 가지고 input값은 가지지 않는 함수로 컴파일러가 인식한 것이다

따라서 이러한 문제를 해결하기 위해 나온 것이 Uniform Initialization 이다

해결코드

#include <iostream>
using namespace std;

class A
{
public:
	A() { std::cout << "A의 생성자 호출!" << std::endl; }
};

int main()
{
	A a{};

	return 0;
}

결과

A의 생성자 호출!

Uniform Initialization 특징

암시적 형변환 즉, 데이터 손실이 있는 (Narrowing) 변환 불가

#include <iostream>
using namespace std;

class A
{
public:
	A(int x) { std::cout << "A의 생성자 호출!" << std::endl; }
};

int main()
{
	A a( 3.5 );	//Narrow-conversion 가능
	A a{ 3.5 };	//Narrow-conversion 불가 -> 빌드 에러 발생
	return 0;
}

   

이와 같이 암시적으로 인자값이 들어가고 손실이 나는 변환(float > int)이 발생하면 빌드 에러를 발생시켜준다 즉, 원하지 않는 타입 캐스팅을 방지할 수 있다

함수 리턴 시 타입을 명시하지 않아도 됨

#include <iostream>
using namespace std;

class A
{
public:
	A(int x, double y) { std::cout << "A의 생성자 호출!" << std::endl; }
};

A func()
{
	return { 2, 3.5 };	//유니폼초기화가 아니면 A(2, 3.5) 와 같이 사용해야 함
}

int main()
{
	func();
	return 0;
}

간단하지만 함수 return 시에 return type을 명시하지 않아도 됨        

2. std::initializer_list(초기화자)

유니폼 초기화를 통해서 간단하게 초기화자를 사용가능

#include <iostream>
#include <initializer_list>
#include <vector>
#include <map>
using namespace std;

class A
{
public:
	A(std::initializer_list<int> l)
	{
		for (auto iter = l.begin(); iter != l.end(); ++iter)
			std::cout << *iter << std::endl;
	}
};


int main()
{
	A a = { 1, 2, 3, 4, 5 };

	//vector 가능
	std::vector<int> v = { 1, 2, 3, 4, 5 };

	//map 가능
	std::map<int, std::string> m =
	{
		{1, "Hello"},
		{2, "world"},
		{3, "Test"}
	};

	return 0;
}

std::initializer_list를 받는 인자가 있으면 유니폼 초기화를 사용해서 인자값을 전달해주는 것이 가능

std::initializer_list의 특징

오버로딩 된 함수 인자중 우선권이 가장 높다

//2개의 생성자가 존재
vector(size_type count);				//1. vector의 원소 개수를 받아서 생성
vector(std::initializer_list<int> l);	//2. initializer_list를 받아서 생성

...

vector v{10};	//인자가 하나라서 1번 생성자를 생성할 것 같지만...

이 예시의 경우 2번을 호출하게 된다
왜냐하면 std::initializer_list를 사용할 경우 우선권이 가장 높기 때문에 컴파일러는 최대한 해당 요소와 맞게 매칭시키려하기 때문

따라서 다음과 같은 상황이 나오게 됨

#include <iostream>
#include <initializer_list>

class A
{
public:
	A(int x, double y)
	{ 
		std::cout << "일반 생성자! " << std::endl;
	}

	A(std::initializer_list<int> lst)
	{
		std::cout << "초기화자 사용 생성자! " << std::endl;
	}
};

int main()
{
	A a(3, 1.5);	// 가능 -> 일반생성자 호출
	A b{ 3, 1.5 };	// 빌드 에러

	return 0;
}

첫번째 생성은 일반적인 생성자라 잘 생성이 되고
두번째 생성때 일반 생성자를 호출해야 할 것 같지만 initializer_list의 우선권이 더 높기 때문에 2번째 생성자가 매칭이되고
그로 인해서 Narrow-conversion 때문에 빌드에러가 발생하게 된다

auto 와 initializer_list 의 관계

uniform 형식으로 auto 를 생성하게 되면 해당 객체는 initializer_list가 됨

//c++ 14까지
auto a = { 1 };     // std::initializer_list<int>
auto b{ 1 };        // std::initializer_list<int>
auto c = { 1, 2 };  // std::initializer_list<int>
auto d{ 1, 2 };     // std::initializer_list<int>

//c++ 17부터
auto a = { 1 };     // std::initializer_list<int>
auto b{ 1 };        // int
auto c = { 1, 2 };  // std::initializer_list<int>
auto d{ 1, 2 };     // 빌드에러

auto b{ 1 };의 형태가 단일 형태인데도 initializer_list로 선언되는 것은 어색했기 때문에 c++ 17부터 문법이 살짝 바뀌었다

auto x = {arg1, arg2...}    //initializer_list
auto x {arg1, arg2, ...}    //arg1 타입으로 선언, 인자값이 2개이상일 경우 에러

   

auto 를 이용해서 initializer_list를 문자열로 받으면 string 형식으로 받아지지 않으니 주의

#include <iostream>
#include <initializer_list>

using namespace std::literals;

int main()
{
	auto charList = { "a", "b", "c" };			//std::initializer_list<const char*>
	auto stringList = { "a"s, "b"s, "c"s };		//std::initializer_list<string>
	return 0;
}

따라서 이 때는 사용자 정의 리터럴을 사용해서 표기해주면 string으로 표시됨

댓글남기기