본문 바로가기

Programming/CPP11&14

[C++11] 초기화자 리스트(initializer list)와 std::initializer_list

반응형

C++11 이전의 초기화에는 다양한 방법이 사용되었습니다.

동일하지 않은 초기화 방식으로 복잡한 C++ 문법을 더 복잡하게 만들었습니다.

C++11에서는 {...}(braced-init-list)를 사용한 동일한 초기화 문법을 제공합니다.

기존에는 구조체나 구조체 배열 등의 경우 다음과 같이 {...}의 리스트로 초기화가 가능했습니다.

#include <iostream>

using namespace std;

struct MyData
{
        int myInt;
        double myDouble;
};

int main()
{
        MyData data[3] = { {1, 3.3}, {2, 4.4}, {3, 10.1} };
   
        cout << data[1].myInt << endl;
        cout << data[2].myDouble << endl;
   
        return 0;
}

구조체의 배열을 중첩된 braced-init-list로 초기화를 하는 방법입니다.

다만 POD(Plain Old Data) 타입 만을 한정적으로 지원하기 때문에 사용에 제약이 있습니다.

std::vector나 std::list를 원하는 값으로 초기화해서 생성하는 방법 등은 제공되지 않았습니다.

C++11에서는 std::initializer_list가 추가되면서 균등한 초기화 방식을 사용할 수 있습니다.

std::initializer_list<T> 객체는 const T 타입 배열에 액세스를 제공하는 경량 프록시 오브젝트입니다.

다음 상황에서 std::initializer_list가 자동으로 생성됩니다.

1. 함수 호출 초기화와 대입 표현식을 포함해서 리스트 초기화에서 braced-init-list를 사용하는 경우

2. Ranged for에서 사용을 포함한 auto에 바인드 되는 경우

std::initializer_list는 두 개의 포인터 혹은 하나의 포인터와 크기를 이용해서 구현이 될 수 있습니다.

주의할 점은 얕은 복사로 std::initializer_list의 복사가 이뤄진다는 점입니다.

원본 초기화 리스트 객체가 파괴되면 복사된 initializer_list의 데이터 무결성을 보장할 수 없습니다.

그리고 초기화자 리스트는 하향 초기화(narrowing conversion)는 오류가 발생할 수 있습니다.

하향 초기화는 int 타입에 double 등의 타입으로 초기화하는 경우입니다.

std::initializer_list를 통해서 {...}(braced-init-list)로 균일한 초기화는 다음과 같이 사용할 수 있습니다.

#include <vector>
#include <initializer_list>
#include <iostream>

template <class T>
struct S
{
        std::vector<T> v;
        S(std::initializer_list<T> l) : v{ l }
        {
                std::cout << "constructed with a " << l.size() << "-element list\n";
        }
        void append(std::initializer_list<T> l)
        {
                v.insert(v.end(), l.begin(), l.end());
        }
        std::pair<const T*, std::size_t> c_arr() const
        {
                return{ &v[0], v.size() };  // return 에서 리스트 초기화로 복사
                                            // std::initializer_list를 사용하진 않음
        }
};

template <typename T>
void templated_fn(T) {}

int main()
{
        S<int> s = { 1, 2, 3, 4, 5 }; // initializer_list로 생성
        s.append({ 6, 7, 8 });      // 함수 호출에서 리스트 초기화

        std::cout << "The vector size is now " << s.c_arr().second << " ints:\n";

        for (auto n : s.v)
                std::cout << n << ' ';
        std::cout << '\n';

        std::cout << "Range-for over brace-init-list: \n";

        for (int x : {-1, -2, -3}) // ranged-for에서 auto 타입(initializer_list)으로 바인딩되서 동작
                std::cout << x << ' ';
        std::cout << '\n';

        auto al = { 10, 11, 12 };   // auto의 특별한 생성 규칙

        std::cout << "The list bound to auto has size() = " << al.size() << '\n';

        //    templated_fn({1, 2, 3}); // compiler error! "{1, 2, 3}" is not an expression,
        // it has no type, and so T cannot be deduced
        templated_fn<std::initializer_list<int>>({ 1, 2, 3 }); // OK
        templated_fn<std::vector<int>>({ 1, 2, 3 });           // also OK

        return true;
}

생성자에서 멤버를 초기화 할 때도 braced-init-list로 초기화를 합니다.

구조체에 std::vector가 존재하는데 바로 initializer_list로 초기화가 되며 함수 호출도 됩니다.

c_arr()을 보면 std::pair 형태로 braced-init-list로 바로 생성할 수 있습니다.

ranged based for문에서도 {...}가 auto(std::initializer_list)에 바인딩됩니다.

다만 주석 처리된 부분을 해제하면 에러가 발생하는데 braced-init-list 자체가 표현식이나 타입이 아니기 때문입니다.

타입이나 표현식이 아니라 템플릿 T의 타입 추론을 할 수 없습니다.

C++11의 {...}(braced-init-list)를 통해서 편리하고 직관적인 초기화가 가능해졌습니다.

반응형