해석된 C++ 목록 초기화 구문

8349 단어 c++목록 초기화

집합 초기화


먼저 std::array의 내부 실현부터 말하자면.std::array를 원생수 그룹처럼 표현하기 위해 C++의 std::array는 다른 STL 용기와 큰 차이가 있습니다. std::array는 어떠한 구조 함수도 정의하지 않았고 모든 내부 데이터 구성원은public입니다.이것은 std::array를 하나의 집합 (aggregate) 으로 만든다.
집합에 대한 정의는 각 C++ 버전에서 약간의 차이가 있다. 여기서 간단하게 요약하면 C++17의 정의는 다음과 같은 조건을 충족시킬 때 하나의 집합이라고 한다[1].
  • 프라이빗 또는 보호된 데이터 구성원이 없습니다
  • 사용자가 제공한 구조 함수가 없습니다(그러나 현식 사용 = default 또는 =delete가 성명한 구조 함수는 제외)
  • 가상,private 또는protected 기류가 없다
  • 허함수가 없다
  • 직관적으로 보면 집합은 항상 데이터만 포함하는struct 유형, 즉 흔히 말하는 POD 유형에 대응한다.또 원생수 그룹의 유형도 모두 집합이다.
    집합 초기화는 괄호 목록을 사용할 수 있습니다.일반적으로 중괄호 안의 요소는 집합된 요소와 일일이 대응하고 중괄호의 중첩도 집합 유형의 중첩과 일치한다.C 언어에서, 우리는 이러한 struct 초기화 문장을 흔히 볼 수 있다.
    위의 원리를 풀면 왜 std::array의 초기화는 한 층의 대괄호가 많을 때 성공할 수 있는지 이해하기 쉽다. std::array 내부의 유일한 원소는 원생수 그룹이기 때문에 두 층의 끼워 넣는 관계가 있다.다음은 사용자 정의 MyArray 형식을 보여 줍니다. 데이터 구조는 std::array와 거의 같고 초기화 방법도 비슷합니다.
    
    struct S {
        int x;
        int y;
    };
    
    template<typename T, size_t N>
    struct MyArray {
        T data[N];
    };
    
    int main()
    {
        MyArray<int, 3> a1{{1, 2, 3}};  //  
        MyArray<S, 3> a2{{{1, 2}, {3, 4}, {5, 6}}};  //  
        return 0;
    }
    위의 예에서 초기화 목록의 가장 바깥쪽 괄호는 MyArray에 대응하고 그 다음 층의 괄호는 데이터 구성원 데이터에 대응하며 그 다음은 데이터의 요소이다.중괄호의 중첩은 유형 간의 중첩과 완전히 일치합니다.이것이야말로 std::array의 엄격하고 완전한 초기화 괄호 쓰기입니다.
    그런데 왜 std::array 원소 유형이 간단한 유형일 때 괄호를 한 겹 줄여도 괜찮아요?이것은 집합 초기화의 또 다른 특징인 대괄호 생략과 관련된다.

    괄호 생략(brace elision)


    C++는 집합된 내부 구성원이 여전히 집합일 때 한 겹 또는 여러 겹의 괄호를 줄일 수 있습니다.괄호가 생략될 때, 컴파일러는 내층 집합에 포함된 원소의 개수에 따라 순서대로 채워진다.
    아래의 코드는 흔하지는 않지만 합법적이다.2차원 수조의 초기화는 한 층의 대괄호만 사용하지만, 대괄호의 생략 특성 때문에 컴파일러는 순서대로 모든 요소로 내층 수조를 채운다.
    
    int a[3][2]{1, 2, 3, 4, 5, 6}; //  {{1, 2}, {3, 4}, {5, 6}}
    대괄호가 생략된 것을 알고 std::array 초기화는 한 층의 대괄호만 사용하는 원리를 알 수 있다. std::array의 내부 구성원 수조는 하나의 집합이기 때문에 컴파일러가 {1,2,3} 같은 목록을 볼 때 하나씩 대괄호 안의 원소를 내부 수조의 원소에 채운다.심지어 std::array 내부에 두 개의 수조가 있다고 가정하면 이전 수조를 다 채운 후에 순서대로 다음 수조를 채울 것이다.
    이것 또한 왜 내부 괄호를 절약하고 복잡한 유형도 컴파일할 수 있는지 설명한다.
    
    std::array<S, 3> a3{1, 2, 3, 4, 5, 6};  //  , 
    S도 집합 유형이기 때문에 여기에 두 개의 괄호가 생략되었다.컴파일러는 다음 순서에 따라 원소를 순서대로 채웁니다. 수조 0호 원소의 S::x, 수조 0호 원소의 S:y, 수조 1호 원소의 S::x, 수조 1호 원소의 S:y...
    비록 대괄호는 생략할 수 있지만, 일단 사용자가 대괄호를 현저하게 썼다면, 반드시 이 층의 원소 개수와 엄격하게 대응해야 한다.따라서 다음 글쓰기는 오류를 보고합니다.
    
    std::array<S, 3> a1{{1, 2}, {3, 4}, {5, 6}};  //  !
    컴파일러는 {1,2}에 대응하는 std::array의 내부 그룹, 그리고 {3,4}에 대응하는 std::array의 다음 내부 구성원이라고 생각합니다.그러나 std::array는 데이터 구성원이 하나밖에 없어서 오류를 보고했습니다. too many initializers for'std::array'
    주의해야 할 것은 괄호 생략은 집합 유형에만 유효하다는 것이다.만약 S에 사용자 정의 구조 함수가 있다면, 괄호를 줄이면 안 된다.
    
    //  
    struct S1 {
        S1() = default;
        int x;
        int y;
    };
    
    std::array<S1, 3> a1{1, 2, 3, 4, 5, 6};  // OK
    
    //  
    struct S2 {
        S2() = delete;
        int x;
        int y;
    };
    
    std::array<S2, 3> a2{1, 2, 3, 4, 5, 6};  // OK
    
    //  , 
    struct S3 {
        S3() {};
        int x;
        int y;
    };
    
    std::array<S3, 3> a3{1, 2, 3, 4, 5, 6};  //  !
    여기서 =default의 구조 함수와 공 구조 함수의 미묘한 차이를 볼 수 있다.

    std::initializer_리스트의 또 다른 이야기


    위에서 말한 모든 규칙은 집합 초기화에만 유효하다.만약 우리가 MyArray 유형에 수용 std를 추가한다면::initializer_list의 구조 함수, 상황은 또 다르다.
    
    struct S {
        int x;
        int y;
    };
    
    template<typename T, size_t N>
    struct MyArray {
    public:
        MyArray(std::initializer_list<T> l)
        {
            std::copy(l.begin(), l.end(), std::begin(data));
        }
        T data[N];
    };
    
    int main()
    {
        MyArray<S, 3> a{{{1, 2}, {3, 4}, {5, 6}}};  // OK
        MyArray<S, 3> b{{1, 2}, {3, 4}, {5, 6}};  //  OK
        return 0;
    }
    std::initializer_list의 구조 함수로 초기화할 때 초기화 목록의 바깥쪽이 한 층이든 두 층의 괄호든 모두 초기화에 성공할 수 있고 a와 b의 내용은 완전히 같다.
    이건 또 왜?설마std::initializer_목록도 괄호 생략을 지원합니까?
    여기서 재미있는 이야기를 하나 하자면, 이 책은 대상의 초기화 방법을 설명할 때 다음과 같은 예를 들었다[2].
    
    class Widget {
    public:
      Widget();                                   // default ctor
      Widget(std::initializer_list<int> il);      // std::initializer_list ctor
      …                                          // no implicit conversion funcs
    }; 
    
    Widget w1;          // calls default ctor
    Widget w2{};        // also calls default ctor
    Widget w3();        // most vexing parse! declares a function!    
    
    Widget w4({});      // calls std::initializer_list ctor with empty list
    Widget w5{{}};      // ditto <- !
    그러나 책에서 이 코드의 마지막 줄 w5에 대한 주석은 기술적 오류입니다.이 w5의 구조 함수를 호출할 때 w4처럼 빈 std::initializer_list, 하나의 요소를 포함하는 std::initializer_list.
    Scott Meyers와 같은 C++ 큰 소도 괄호의 의미에서 틀릴 수 있습니다. C++의 관련 규칙이 함정으로 가득 차 있음을 알 수 있습니다!

    'Effective Modern C++'마저도 틀린 규칙


    다행히도'Effective Modern C++'는 고전 도서로 독자가 많다.곧 독자들이 이 오류를 발견했고 이후 스콧 마이어스는 이 오류에 대한 논술을 책의 오류표에 넣었다.[3]
    Scott Meyers는 독자들에게 그와 함께 정확한 규칙이 도대체 무엇인지 연구하도록 초청했다. 마지막으로 그들은 결론을 한 문장에 썼다[4].문장은 서로 다른 구조 함수를 가진 3가지 사용자 정의 유형을 통해 std::initializer_list가 일치할 때의 미묘한 차이.코드는 다음과 같습니다.
    
    #include <iostream>
    #include <initializer_list>
     
    class DefCtor {
      int x;
    public:
      DefCtor(){}
    };
     
    class DeletedDefCtor {
      int x;
    public:
      DeletedDefCtor() = delete;
    };
     
    class NoDefCtor {
      int x;    
    public:
      NoDefCtor(int){}
    };
     
    template<typename T>
    class X {
    public:
      X() { std::cout << "Def Ctor
    "; } X(std::initializer_list<T> il) { std::cout << "il.size() = " << il.size() << '
    '; } }; int main() { X<DefCtor> a0({}); // il.size = 0 X<DefCtor> b0{{}}; // il.size = 1 X<DeletedDefCtor> a2({}); // il.size = 0 // X<DeletedDefCtor> b2{{}}; // error! attempt to use deleted constructor X<NoDefCtor> a1({}); // il.size = 0 X<NoDefCtor> b1{{}}; // il.size = 0 }
    구조 함수가 삭제된 비중합 형식에 대해 {}로 초기화하면 컴파일 오류가 발생하기 때문에 b2의 표현은 이해하기 쉽습니다.그러나 b0과 b1의 차이는 이상하다. 똑같은 초기화 방법, 왜 std::initializer_list의 길이는 1이고, 다른 길이는 0입니까?

    구조 함수의 2단계 시도


    문제는 대괄호 초기화를 사용하여 구조 함수를 호출할 때 컴파일러가 두 번 시도하기 때문입니다.
    1. 전체 괄호 목록을 가장 바깥쪽 괄호와 함께 구조 함수로 하는 std::initializer_list 매개 변수, 일치하는지 확인하기;
    2. 첫 번째 단계가 실패하면 괄호 목록의 구성원을 구조 함수의 인참으로 삼아 일치하는지 확인합니다.
    b0 {}}와 같은 표현식에 대해 첫 번째 시도는 다음과 같다. b0 ({{}}}), 즉 {{}} 전체를 하나의 매개 변수로 구조 함수에 전달하는 것이다.b0에 대해 말하자면, 이 일치는 성공할 수 있다.DefCtor는 {}를 통해 초기화할 수 있기 때문에 b0의 초기화는 X(std::initializer_list)를 호출하고 1명의 구성원을 포함하는 std:::initializer_입참하다.
    b1{{}}에 대해 컴파일러도 첫 번째 시도를 하지만 NoDefCtor는 {}로 초기화하는 것을 허용하지 않기 때문에 첫 번째 시도는 실패합니다.다음 컴파일러는 두 번째 시도를 합니다. 바깥쪽 괄호를 벗기고 b1({})을 호출하면 성공할 수 있습니다. 이때 전송된 것은 빈 std::initializer_list.
    이전의 My Array의 예를 돌이켜 보면 현재 우리는 두 가지 초기화가 각각 어느 단계에서 성공했는지 분석할 수 있다.
    
    MyArray<S, 3> a{{{1, 2}, {3, 4}, {5, 6}}};  //  , 
    MyArray<S, 3> b{{1, 2}, {3, 4}, {5, 6}};  //  

    종합 소테스트


    여기까지 괄호가 각종 장면에서 초기화되는 규칙은 모두 해석되었다.독자들이 철저히 파악했는지 모르겠다.
    다음 작은 테스트를 시도해 보십시오. 이 코드에는 하나의 요소만 포함하는 std::array가 있습니다. 그 요소 유형은 std::tuple,tuple는 구성원이 하나입니다. 사용자 정의 형식 S입니다. S 정의는 기본 구조 함수와 std::initializer_list의 구조 함수입니다.이 유형에 대해 초기화할 때 몇 개의 괄호를 사용할 수 있습니까?아래의 초기화 문장은 어떤 것이 성공할 수 있습니까?각각 왜?
    
    struct S {
        S() = default;
        S(std::initializer_list<int>) {}
    };
    
    int main()
    {
        using MyType = std::array<std::tuple<S>, 1>;
        MyType a{};             // 1 
        MyType b{{}};           // 2 
        MyType c{{{}}};         // 3 
        MyType d{{{{}}}};       // 4 
        MyType e{{{{{}}}}};     // 5 
        MyType f{{{{{{}}}}}};   // 6 
        MyType g{{{{{{{}}}}}}}; // 7 
        return 0;
    }
    이상은 바로 해석된 C++의 목록 초기화 문법에 대한 상세한 내용입니다. C++의 목록 초기화 문법에 대한 더 많은 자료는 저희 다른 관련 글을 주목해 주십시오!

    좋은 웹페이지 즐겨찾기