해석된 C++ 목록 초기화 구문
집합 초기화
먼저 std::array의 내부 실현부터 말하자면.std::array를 원생수 그룹처럼 표현하기 위해 C++의 std::array는 다른 STL 용기와 큰 차이가 있습니다. std::array는 어떠한 구조 함수도 정의하지 않았고 모든 내부 데이터 구성원은public입니다.이것은 std::array를 하나의 집합 (aggregate) 으로 만든다.
집합에 대한 정의는 각 C++ 버전에서 약간의 차이가 있다. 여기서 간단하게 요약하면 C++17의 정의는 다음과 같은 조건을 충족시킬 때 하나의 집합이라고 한다[1].
집합 초기화는 괄호 목록을 사용할 수 있습니다.일반적으로 중괄호 안의 요소는 집합된 요소와 일일이 대응하고 중괄호의 중첩도 집합 유형의 중첩과 일치한다.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
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++의 목록 초기화 문법에 대한 더 많은 자료는 저희 다른 관련 글을 주목해 주십시오!이 내용에 흥미가 있습니까?
현재 기사가 여러분의 문제를 해결하지 못하는 경우 AI 엔진은 머신러닝 분석(스마트 모델이 방금 만들어져 부정확한 경우가 있을 수 있음)을 통해 가장 유사한 기사를 추천합니다:
c++ 파일의 줄별 읽기와 문자 일치 실현예를 들어 다음과 같은 이름은mindspore입니다.txt 파일 (이 이름을 지은 이유는 최근에 마인드spore를 연구하고 있기 때문에 가장 쉽게 얻을 수 있는 데이터는 마인드spore의 핑계api 문서입니다): 그...
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
CC BY-SA 2.5, CC BY-SA 3.0 및 CC BY-SA 4.0에 따라 라이센스가 부여됩니다.