Uniform Initialization, Initializer list

하루공부·2024년 1월 20일
0

C++

목록 보기
14/25
post-thumbnail

C++ 아이콘 제작자: Darius Dan - Flaticon


Uniform Initialization

  • 균일한 초기화는 생성자를 호출하기 위해 () 대신 {}를 사용하는 것이다.

    생성자를 호출하려다 함수를 만들어 버리거나 그런 실수를 방지할 수 있다.
    그런데 ()와 달리 일부 암시적 타입 변환들을 불허한다.

    class A {
    public:
    	A(int x) { std::cout << "A 의 생성자 호출!" << std::endl; }
    };
    int main() {
    	A a(3.5); // Narrow-conversion 가능
    	A b{ 3.5 }; // Narrow-conversion 불가 }

    ==> 따라서 {} 를 사용하면 위와 같이 원하지 않는 타입 캐스팅을 방지해서 미연에 오류를 잡아낼 수 있다.


  • 또 다른 쓰임새로 함수 리턴 시에 굳이 생성하는 객체의 타입을 다시 명시 하지 않아도 된다.
class A {
public:
	A(int x, double y) { std::cout << "A 생성자 호출" << std::endl; }
};

A func() {
	return { 1, 2.3 }; // A(1, 2.3) 과 동일 }
	// {}를 생성하지 않았다면 A(1, 2.3) 처럼 클래스를 명시해줘야지만
	// {}를 이용할 경우 컴파일러가 알아서 함수의 리턴 타입을 보고 추론한다.

Initializer list

  • initializer_list 는 우리가 {} 를 이용해서 생성자를 호출할 때
    클래스의 생성자들 중에 initializer_list 를 인자로 받는 생성자가 있다면 전달된다.

    만약 ()로 생성자를 호출했다면 initializer_list는 생성되지 않는다.
    initializer_list를 활용하면 컨테이너들을 간단하게 정의할 수 있다.

template <typename T>
void print_vec(const std::vector<T>& vec) {
	std::cout << "[";
	for (const auto& e : vec) { std::cout << e << " "; }
	std::cout << "]" << std::endl;
}

template <typename K, typename V>
void print_map(const std::map<K, V>& m) {
	for (const auto& kv : m) { std::cout << kv.first << " : " << kv.second << std::endl; }
}

int main() {
	std::vector<int> v = { 1, 2, 3, 4, 5 };
	print_vec(v);
	std::cout << "----------------------" << std::endl;
	std::map<std::string, int> m = { {"abc", 1}, {"hi", 3}, {"hello", 5}, {"c++", 2}, {"java", 6} };
	print_map(m);
}

컨테이너를 초기화할 때 간단하게 {} 안에 원소들을 컨테이너에 맞게 넣어주면 된다.


  • initializer_list를 받는 생성자가 있다면 1가지 주의해야 할 점이 있다.
    ==> 만일 {} 를 이용해서 객체를 생성할 경우 생성자 오버로딩 시에 해당 함수가 최우선으로 고려된다.
    ==> 설령 호출시 인자가 알맞지 않더라도...
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 }; // {}를 사용해서  initializer_list를 받는 생성자를 젤 우선적으로 생각함
    }

최우선적으로 생각하여 해당 생성자를 선택하여 오버로딩 했지만
{}로 호출했기에 타입변환이 불허되기 때문에 컴파일 오류가 난다.

전달되는 인자를 보면 일반 생성자가 호출되는게 맞다고 생각하지만
최우선은 initializer_list 를 이용한 생성자를 최대한 고려하기 때문

하지만 아예 암시적 타입 변환이 일어나지 않는 경우는 해당 문제가 없다

A(std::initializer_list<std::string> lst) { std::cout << "초기화자 사용 생성자! " <<   std::endl; } };
A b{3, 1.5}; // 일반 생성자

위 경우 double이 string으로 변환될 수 없어서 그냥 오버로딩 후보군에서 제외된다.


  • {} 를 이용해서 생성할 때 타입으로 auto 를 지정한다면 initializer_list 객체가 생성

    C++ 17 부터 아래와 같이 두 가지 형태로 구분해서 auto 타입이 추론

    auto x = {arg1, arg2...} 형태의 경우 arg1, arg2 ... 들이 모두 같은 타입이라면 x 는 std::initializer_list<\T>
    로 추론된다
    auto x {arg1, arg2, ...} 형태의 경우 만일 인자가 단 1 개라면 인자의 타입으로 추론되고, 여러 개일 경우 오류를 발생시킨다.


• 한 가지 주의할 점은 auto list = {"a", "b", "cc"}; 이는 initializer_list 이 아닌 initializer_list 이 된다는 점

C++ 14 에서 추가된 리터럴 연산자를 통해 해결 ==> std::literals
auto list = {"a"s, "b"s, "c"s}; 와 같이 하면, initializer_list<std::string> 으로 추론


공부한 내용 복습

개인 공부 기록용 블로그입니다.
틀린 부분 있으다면 지적해주시면 감사하겠습니다!!

0개의 댓글