簡體   English   中英

為什么為聯合或類似聯合的 class 刪除默認的默認構造函數?

[英]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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM