본문으로 바로가기

C++11) 초기화 리스트(initialize_list)

category C++/Modern 2019. 1. 27. 01:00

 <기본 정리>


초기화 리스트(initialize_list)는 C++11에서 도입된 방식이다.

이전 C++98 에서는 Member Initializer list ( 콜론 초기화 ) 를 통해서 멤버 변수들 초기화가 가능했으며

C++ 11 부터는 Braced-Init-list를 지원한다.

기본적으로 구조체 혹은 클래스로 만들어진 사용자 정의 타입에 대해 동작하며, 컨테이너 및 STL에는 std::initializer_list<> 로 구현되어있다.


http://www.enseignement.polytechnique.fr/informatique/INF478/docs/Cpp/en/cpp/language/initializer_list.html


 <테스트에 사용된 소스>



참고) U라는 클래스 내에서 T라는 타입의 멤버변수를 초기화 할때도 사용할 수 있다. ( 18번째 줄 )


출력결과


복사 생성자 또한 생성자의 한 종류이기 때문에 초기화 리스트를 작성할 수 있다.

소스내에선 깜빡하고 작성하지 않았지만, cItem MyItem(1,1,"abc")도 가능하다.


<초기화리스트의 장점>


기존에 멤버변수를 초기화 해야한다면, 대충 생성자의 소스는 이럴 것이다.


class cItem{

  int a;

  int b;

  ....

public:

 cItem(int _a, int _b,....){

  a = _a;

  b = _b;

 }

};


1. 생성자가 길어지는걸 줄일 수 있다.


2. 멤버변수를 효율적으로 초기화 시킬 수 있다.

예를 들어 멤버변수내에 std::string 타입의 변수가 있다면, 초기화 리스트를 사용하지 않으면

mystring = _string; 

이런식으로 생성자내에서 초기화를 시켜줘야한다.

이때, mystring은 std::string의 기본생성자를 통해 한 번 호출된 후, _string 을 대입하며 대입 연산자가 한번 호출 된다.

하지만 초기화 리스트를 사용하면 std::string의 기본생성자를 호출하지 않고 입력받은 값으로 한번에 객체를 생성하게 된다.


초기화 리스트를 사용하지 않았을 때의 소스 


결과 : 멤버변수를 만들기 위해 기본 생성자를 호출한 후, 대입 연산자를 호출한다.


초기화 리스트를 사용했을 때의 소스

(누락된 점이 있는데.. 위 소스에서는 복사 생성자를 구현 안해줬다.. 깜빡했다.. 하아..)


초기화 리스트를 사용했기 때문에, 기본 생성자를 호출하여 임시 변수를 만들 필요 없이, 한번에 초기화 된다.


이처럼 불필요한 임시변수를 만들고, 그것에 대입하는 불필요한 연산을 줄이고 획득시 초기화 (RAII) 를 사용할 수 있게 한다.

★ RAII에 대한 정보 : http://occamsrazr.net/tt/297 (류광님 블로그)


3. 변수가 순서대로 선언됐다는 가정하에, 확실한 초기화를 보장할 수 있다.


 class A{

private:
    OtherClass var1;
    AnotherClass var2;
public:
    A(OtherClass o, int y) :
    var1(o), var2(var1, y) { }

};


위 클래스는 var2를 초기화 하기위해 var1을 받는다, 이 때 var1이 var2보다 먼저 선언됐다면, var1이 먼저 초기화 된다.

( var1과 var2의 생성 순서가 다르면, 오류가 발생한다. )



 <심화 내용>


1. ) 초기화 리스트를 지원하는 범위 :

★정보 : https://code.i-harness.com/ko-kr/docs/cpp/language/list_initialization


<직접 초기화>

T 객체 { arg1, arg2, ... };   // 1

T { arg1, arg2, ... };    // 2

new T { arg1, arg2, ... }   // 3

class{ T member { arg1, arg2, ... }; }; }; };  // 4

class :: 클래스 () : 멤버 { arg1, arg2, ... } {...   // 5





<복사 초기화>

T 객체 = { arg1, arg2, ... };   // 6

함수 ( { arg1, arg2, ... } ) ;   // 7

return { arg1, arg2, ... } ;   // 8

object [ { arg1, arg2, ... } ] ;   // 9

object = { arg1, arg2, ... } ;   // 10

U ( { arg1, arg2, ... } )   // 11

class { T member = { arg1, arg2, ...}; }; }; };   // 12



1. 초기화 리스트 ( 즉, 중괄호로 묶인 표현식 또는 중첩된 초기화 리스트 )에 있는 변수들 초기화 예 ) cItem Item { 1,2,"abc" }

2. 무명 객체 초기화 리스트 

3. new 를 사용한 동적 할당 객체에 대한 초기화 리스트 

4. 등호를 사용하지 않는 비 정적(non - static) 멤버 변수 ( 정보 : https://code.i-harness.com/ko-kr/docs/cpp/language/data_members#Member_initialization )

5. 초기화 리스트가 사용된 생성자 내에서의 또다른 멤버변수가 초기화 리스트를 사용할 때 ( 초기화 리스트 중첩 )


6. 초기화 리스트를 사용하되, 등호를 이용한 초기화를 할 시 (이건 저도 잘 모르겠습니다. 위에 소스에선 직접적인 초기화로 들어왔는데 왜...?)

7. 함수에 집어넣는 초기화 리스트

8. 값을 반환하는 함수 내에서 사용된 무명 객체의 초기화 리스트

9. 첨자 연산(operator[]) 에서 오버로드된 연산자의 매개변수 초기화 리스트 (???)

10. 대입 연산자를 오버로드한 매개변수 초기화 리스트

11. 기능적 캐스트 표현식에서 초기화 리스트 ( 여기서 초기화 리스트는 생성자 인수 대신 사용됨 )

12. 등호 기호를 사용하는 비 정적 (non - static) 멤버 변수의 초기화


2. ) 선언과 동시에 초기화해야 하는 변수

참조자 멤버 변수( int& a )

비 정적 상수 변수 ( const int a )

기본 생성자가 없는 사용자 정의타입의 멤버 변수


이땐 class 내에서 기본적으로 초기화 시켜줄 수 없다.

그러므로 꼭 선언만 하고, 초기화 리스트에서 초기화를 시켜줘야 한다.


3. ) T 형의 객체의 리스트 초기화의 효과


<C++14 이후>

(1) T가 배열 또는 클래스 형이고, 초기화리스트에서 초기화가 동일하게 일어나거나 파생된 유형이면 복사 초기화를 진행합니다. 

(위에서 복사 초기화를 호출했던 소스 참조)

그렇지 않고 T가 문자 배열이고, 초기화리스트에 문자열 리터럴이 있다면 평소의 문자열 초기화 처럼 진행합니다.


(2) T가 배열 또는 클래스 형이면 초기화리스트 초기화를 합니다. 

초기화 리스트가 비어있고, T가 기본 생성자가 있는 경우라면 값 초기화가 일어납니다. (인수를 받아 대입하는 초기화)


<C++14 이전>

(1) 초기화 리스트가 비어있고, T가 기본 생성자가 있는 경우라면 값 초기화가 일어납니다. (인수를 받아 대입하는 초기화)

그렇지 않고 T가 배열 또는 클래스 형이면 초기화리스트 초기화를 합니다. 


줄에 따라 우선순위가 나뉩니다.


4. ) <std::initializer_list>

만약 T가 std::initializer_list에 특수화 돼있는경우, T는 std::initializer_list에 의해 직접초기화 됩니다.

그렇지 않다면, 두 단계로 나뉘어 집니다.

1-) std::initializer_list 를 인수로 갖는 모든 생성자, 또는 기본값을 갖는 생성자는 오버로드에 의해 std::initializer_list로 초기화 됩니다.

2-) 이전 단계에서 오버로드가 되지 않는다면, 복사 목록 초기화 방법중 가장 맞는 방법을 호출하고, 만약 이 초기화 방법이 호출된다면 컴파일이 실패합니다.


5. ) 초기화 리스트는 암시적 변환을 제한합니다. ( 매개변수와 입력하는 인수의 타입이 다른 경우)


1-) 부동소수점 형에서 정수형으로의 변환 ( 매개변수가 double 형이였으나, int 형을 인수로 넣는 경우 )

2-) long double에서 double 또는 float 형으로, double 형에서 float 형으로의 변환

3-) 정수형에서 부동소수점 형으로의 변환

4-) 정수형에서 범위가 지정되지않은 enum형(열거체)로, 대신 enum 형이 상수형이라면 가능합니다.


또한 초기화 리스트는 오버로드 규칙을 따릅니다.

★정보 : https://code.i-harness.com/ko-kr/docs/cpp/language/overload_resolution#Implicit_conversion_sequence_in_list-initialization





글을쓰고 수정하면서 느낀건데, 나 글 진짜 못쓰는구나

심화내용의 대부분은 https://code.i-harness.com/ko-kr/docs/cpp/language/list_initialization 에서 발췌 했습니다.


'C++ > Modern' 카테고리의 다른 글

C++11) std::forward (perfect forwarding)  (4) 2019.02.02
c++11) std::move (move semantics)  (2) 2019.02.01
C++11) auto 키워드  (0) 2019.01.30
C++11) 범위 기반 for문 (loop)  (4) 2019.01.27
emplace_back 과 push_back 의 차이  (10) 2019.01.26