Uniform Initialization
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으로 표시됨
댓글남기기