[英]Why is the defaulted default constructor deleted for a union or union-like class?
struct A{
A(){}
};
union C{
A a;
int b = 0;
};
int main(){
C c;
}
在上面的代碼中, GCC 和 Clang都抱怨 union C
的默認構造函數被定義為已刪除。
但是,相關規則說:
class X 的默認默認構造函數在以下情況下定義為已刪除:
- X 是一個聯合,它有一個帶有非平凡默認構造函數的變體成員,並且 X 的任何變體成員都沒有默認成員初始化器,
- X 是一個非聯合 class ,它有一個變體成員 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的語法糖
即int b = 0;
基本上相當於用 mem-initializer 和空體編寫構造函數:
C() : b{/*but use copy-initialization*/ 0} {}
順便說一句,確保聯盟的最多一個變體成員具有 NSDMI 的規則在某種程度上隱藏在class.union.anon的子條款中:
4 - [...] 聯合的最多一個變體成員可能有一個默認成員初始化器。
我的假設是,由於 gcc 和 Clang 已經允許上述內容(非平凡工會成員上的 NSDMI),他們沒有意識到他們需要更改其實現以獲得完整的 Z87BED571FFA40AAAD7BBA33E38E6 支持。
這在 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 ,它使我們在隱式定義的構造函數中循環回到 SO,由於變體成員 N3690/N47140 與 N4659/N4690/N47140/而被刪除。 我找不到 gcc 的相應錯誤。
請注意, MSVC 正確接受,並將c
初始化為.b = 0
,這在dcl.init.aggr 中是正確的:
5 - [...] 如果聚合是聯合並且初始化列表為空,則
- 5.4 - 如果任何變體成員具有默認成員初始化器,則該成員從其默認成員初始化器初始化; [...]
聯合是一件棘手的事情,因為所有成員共享相同的 memory 空間。 我同意,規則的措辭不夠清楚,因為它忽略了顯而易見的:為多個聯合成員定義默認值是未定義的行為,或者應該導致編譯器錯誤。
考慮以下:
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會在你嘗試實例化union的時候報錯。
請注意,非常不建議擁有不兼容或至少不相關的聯合成員。 這樣做會破壞類型安全,並且會公開邀請程序中的錯誤。 類型雙關是可以的,但對於其他用例,應該考慮使用 std::variant<>。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.