통합 또는 통합 유사 클래스에 대해 기본 생성자가 삭제되는 이유는 무엇입니까?
struct A{
A(){}
};
union C{
A a;
int b = 0;
};
int main(){
C c;
}
위의 코드에서 GCC와 Clang은 모두 공용체의 기본 생성자 C
가 삭제 된 것으로 정의되어 있다고 불평합니다 .
그러나 관련 규칙은 다음과 같이 말합니다.
클래스 X의 기본 생성자는 다음과 같은 경우 삭제 된 것으로 정의됩니다.
- X는 중요하지 않은 기본 생성자가있는 변형 멤버가 있고 X의 변형 멤버에는 기본 멤버 이니셜 라이저가없는 공용체입니다 .
- X는 중요하지 않은 기본 생성자가있는 변형 멤버 M이 있고 M을 포함하는 익명 공용체의 변형 멤버가 기본 멤버 이니셜 라이저 가없는 비 유니온 클래스입니다 .
강조된 문구에 주목하십시오. IIUC 예제에서 변형 멤버 b
에는 기본 멤버 이니셜 라이저가 있으므로 기본 기본 생성자는 삭제 된 것으로 정의되어서는 안됩니다. 이러한 컴파일러가이 코드를 잘못된 형식으로보고하는 이유는 무엇입니까?
정의를 다음 C
으로 변경하면
union C{
A a{};
int b;
};
그러면 모든 컴파일러 가이 코드를 컴파일 할 수 있습니다. 이 동작은 규칙이 실제로 의미하는 바를 암시합니다.
X가 아닌 사소한 기본 생성자와 변형 부재에 대한 공급 초기화없이 기본 구성원이 노동 조합이다 변형 구성원을
이것은 컴파일러 버그입니까, 아니면 그 규칙의 모호한 표현입니까?
답변
이것은 CWG 2084 를 통해 C ++ 14와 C ++ 17 사이에서 변경되었으며 , 이는 (모든) 공용체 멤버의 NSDMI가 기본 기본 생성자를 복원 할 수 있도록 허용하는 언어를 추가했습니다.
CWG 2084에 수반되는 예는 귀하와 미묘하게 다릅니다.
struct S {
S();
};
union U {
S s{};
} u;
여기서 NSDMI는 중요하지 않은 멤버에있는 반면 C ++ 17에 채택 된 문구는 모든 멤버 의 NSDMI가 기본 생성자를 복원 할 수 있도록합니다 . DR에 기록 된대로
NSDMI는 기본적으로 mem-initializer의 구문 설탕입니다.
즉, NSDMI on int b = 0;
은 기본적으로 mem-initializer 및 빈 본문을 사용하여 생성자를 작성하는 것과 같습니다.
C() : b{/*but use copy-initialization*/ 0} {}
옆으로, 그 보장 원칙으로 최대 노조의 한 변형 멤버는 NSDMI 약간의 절에 숨겨져있다 class.union.anon :
4-[...] 공용체의 Variant 멤버는 기본 멤버 이니셜 라이저를 가질 수 있습니다.
내 가정은 gcc와 Clang이 이미 위 (중요하지 않은 공용체 멤버의 NSDMI)를 허용했기 때문에 완전한 C ++ 17 지원을 위해 구현을 변경해야한다는 것을 인식하지 못했다는 것입니다.
이것은 2016 년에 std-discussion 목록에서 논의되었으며 , 귀하와 매우 유사한 예가 있습니다.
struct S {
S();
};
union U {
S s;
int i = 1;
} u;
결론은 clang과 gcc가 거부에 결함이 있다는 것입니다. 당시에는 오해의 소지가있는 메모가 있었지만 결과적으로 수정 되었습니다.
Clang의 경우 버그는 https://bugs.llvm.org/show_bug.cgi?id=39686이는 변형 멤버 N3690 / N4140 대 N4659 / N4727로 인해 삭제 된 암시 적으로 정의 된 생성자 에서 SO로 다시 돌아 갑니다 . gcc에 해당하는 버그를 찾을 수 없습니다.
참고 MSVC가 제대로 수용 하고,이 초기화 c
에 .b = 0
있다, dcl.init.aggr 당 올바른 :
5-[...] 집계가 공용체이고 이니셜 라이저 목록이 비어 있으면
- 5.4-변형 멤버에 기본 멤버 이니셜 라이저가있는 경우 해당 멤버는 기본 멤버 이니셜 라이저에서 초기화됩니다. [...]
모든 구성원이 동일한 메모리 공간을 공유하기 때문에 공용체는 까다로운 것입니다. 동의합니다. 규칙의 문구는 분명하지 않기 때문에 명확하지 않습니다. 하나 이상의 유니온 멤버에 대해 기본값을 정의하는 것은 정의되지 않은 동작이거나 컴파일러 오류로 이어져야합니다.
다음을 고려하세요:
union U {
int a = 1;
int b = 0;
};
//...
U u; // what's the value of u.a ? what's the value of u.b ?
assert(u.a != u.b); // knowing that this assert should always fail.
이것은 분명히 컴파일되지 않아야합니다.
A에는 명시 적 기본 생성자가 없기 때문에이 코드는 컴파일됩니다.
struct A
{
int x;
};
union U
{
A a; // this is fine, since you did not explicitly defined a
// default constructor for A, the compiler can skip
// initializing a, even though A has an implicit default
// constructor
int b = 0;
};
U u; // note that this means that u.b is valid, while u.a has an
// undefined value. There is nothing that enforces that
// any value contained by a struct A has any meaning when its
// memory content is mapped to an int.
// consider this cast: int val = *reinterpret_cast<int*>(&u.a)
A :: x에는 명시적인 기본값이 있기 때문에이 코드는 컴파일 할 수 없습니다. 이것은 U :: b의 명시 적 기본값과 충돌합니다 (의도 된 말장난).
struct A
{
int x = 1;
};
union U
{
A a;
int b = 0;
};
// Here the definition of U is equivalent to (on gcc and clang, but not for MSVC, for reasons only known to MS):
union U
{
A a = A{1};
int b = 0;
};
// which is ill-formed.
이 코드는 거의 같은 이유로 gcc에서 컴파일되지 않지만 MSVC에서 작동합니다 (MSVC는 항상 gcc보다 약간 덜 엄격하므로 놀랍지 않습니다).
struct A
{
A() {}
int x;
};
union U
{
A a;
int b = 0;
};
// Here the definition of U is equivalent to:
union U
{
A a = A{}; // gcc/clang only: you defined an explicit constructor, which MUST be called.
int b = 0;
};
// which is ill-formed.
선언 또는 인스턴스화 지점에서 오류가보고되는 위치는 컴파일러에 따라 다르며, gcc 및 msvc는 초기화 지점에서 오류를보고하고, 통합을 인스턴스화하려고하면 clang이 오류를보고합니다.
비트 호환이되지 않거나 최소한 관련성이있는 공용체의 구성원을 갖는 것은 매우 바람직하지 않습니다. 이렇게하면 형식 안전성이 깨지고 프로그램에 버그가 발생할 수 있습니다. 유형 punning은 괜찮지 만 다른 사용 사례의 경우 std :: variant <> 사용을 고려해야합니다.