簡體   English   中英

C ++ 11奇怪的大括號初始化行為

[英]C++11 strange brace initialization behavior

我不明白C ++ 11大括號初始化規則是如何工作的。 有這個代碼:

struct Position_pod {
    int x,y,z;
};

class Position {
public:
    Position(int x=0, int y=0, int z=0):x(x),y(y),z(z){}
    int x,y,z;
};

struct text_descriptor {
    int             id;
    Position_pod    pos;
    const int       &constNum;
};

struct text_descriptor td[3] = {
     {0, {465,223}, 123},
     {1, {465,262}, 123},
};

int main() 
{
    return 0;
}

注意,該數組聲明有3個元素,但只提供了2個初始值設定項。

然而,它編譯沒有錯誤,這聽起來很奇怪,因為最后一個數組元素的引用成員將是未初始化的。 實際上,它具有NULL值:

(gdb) p td[2].constNum 
$2 = (const int &) @0x0: <error reading variable>

現在是“神奇”:我將Position_pod改為Position

struct text_descriptor {
    int             id;
    Position_pod    pos;
    const int       &constNum;
};

成為這個:

struct text_descriptor {
    int             id;
    Position        pos;
    const int       &constNum;
};

現在它給出了預期的錯誤:

error: uninitialized const member ‘text_descriptor::constNum'

我的問題:為什么它在第一種情況下編譯,何時應該給出錯誤(如第二種情況)。 區別在於,Position_pod使用C樣式的大括號初始化,而Position使用C ++ 11樣式初始化,它調用Position的構造函數。 但是,這如何影響將參考成員保持未初始化的可能性?

(更新)編譯器:gcc(Ubuntu 4.8.2-19ubuntu1)4.8.2

很明顯

 struct text_descriptor td[3] = { {0, {465,223}, 123}, {1, {465,262}, 123}, }; 

是列表初始化,並且初始化列表不為空。

C ++ 11說([dcl.init.list] p3):

列表初始化對象或類型T引用定義如下:

  • 如果初始化列表沒有元素且T是具有默認構造函數的類類型,則對象是值初始化的。
  • 否則,如果T是聚合,則執行聚合初始化(8.5.1)。
  • ...

[dcl.init.aggr] P1:

聚合是一個數組或類(第9節),沒有用戶提供的構造函數(12.1),非靜態數據成員(9.2)沒有大括號或等於初始值,沒有私有或受保護的非靜態數據成員(第11條),沒有基類(第10條),沒有虛函數(10.3)。

td是一個數組,因此它是一個聚合,因此執行聚合初始化。

[dcl.init.aggr] P7:

如果列表中的initializer-clause少於聚合中的成員,則未顯式初始化的每個成員都應從空的初始化列表(8.5.4)初始化。

這是這種情況,因此td[2]從空的初始化列表初始化,([dcl.init.list] p3再次)表示它是值初始化的。

反過來,價值初始化意味着([dcl.init] p7):

對值類型T的對象進行值初始化意味着:

  • 如果T是一個(可能是cv限定的)類類型(第9條),帶有用戶提供的構造函數(12.1),...
  • 如果T是一個(可能是cv限定的)非聯合類類型而沒有用戶提供的構造函數,那么該對象是零初始化的,如果T的隱式聲明的默認構造函數是非平凡的,則調用該構造函數。
  • ...

你的類text_descriptor是一個沒有用戶提供的構造函數的類,所以td[2]首先進行零初始化,然后調用它的構造函數。

零初始化意味着([dcl.init] p5):

零初始化 T類型的對象或引用意味着:

  • 如果T是標量類型(3.9),...
  • 如果T是(可能是cv限定的)非聯合類類型,則每個非靜態數據成員和每個基類子對象都是零初始化的,並且填充初始化為零位;
  • 如果T是(可能是cv認證的)聯合類型,......
  • 如果T是數組類型,...
  • 如果T是引用類型,則不執行初始化。

無論text_descriptor的默認構造函數如何,這都是明確定義的:它只是初始化非引用成員和子成員。

然后調用默認構造函數,如果它是非平凡的。 這是默認構造函數的定義方式([special] p5):

對於A類默認構造函數 X是類的構造函數X ,可以不帶參數調用。 如果類X沒有用戶聲明的構造函數,則沒有參數的構造函數被隱式聲明為默認值(8.4)。 隱式聲明的默認構造函數是其類的內聯公共成員。 如果出現以下情況,則將類X默認默認構造函數定義為已刪除:

  • ...
  • 任何沒有大括號或等號初始化程序的非靜態數據成員都是引用類型,
  • ...

如果默認構造函數不是用戶提供的,則默認構造函數是微不足道的,如果:

  • 它的類沒有虛函數(10.3),也沒有虛基類(10.1),和
  • 沒有類的非靜態數據成員有一個大括號或等於初始化器,和
  • 它的所有直接基類都有簡單的默認構造函數,和
  • 對於類類的所有非靜態數據成員(或其數組),每個這樣的類都有一個普通的默認構造函數。

否則,默認構造函數是非平凡的。

因此,如預期的那樣,隱式定義的構造函數被刪除, 如果pos是POD類型(!),它也是微不足道的。 因為構造函數是微不足道的,所以不會調用它。 因為沒有調用構造函數,所以它被刪除的事實不是問題。

這是C ++ 11中的一個漏洞,已經修復了。 它碰巧已被修復以處理難以訪問的普通默認構造函數 ,但固定的措辭也涵蓋了刪除的普通默認構造函數。 N4140(大致是C ++ 14)在[dcl.init.aggr] p7(強調我的)中說:

  • 如果T是一個(可能是cv限定的)類類型而沒有用戶提供或刪除的默認構造函數,那么該對象是零初始化 ,並且檢查默認初始化的語義約束 ,如果T有一個非平凡的默認構造函數,該對象是默認初始化的;

正如TC在評論中指出的那樣, 另一個DR也發生了變化,因此td[2]仍然是從一個空的初始化列表初始化,但是這個空的初始化列表現在意味着聚合初始化。 反過來,這意味着每個td[2]的成員也從一個空的初始化列表初始化([dcl.init.aggr] p7),所以似乎從{}初始化了引用成員。

[dcl.init.aggr] p9然后說(正如雷米亞貝爾在一個現已刪除的答案中指出的那樣):

如果不完整或空的初始化列表使引用類型的成員未初始化,則該程序格式錯誤。

我不清楚這適用於從隱式{}初始化的引用,但編譯器確實將其解釋為這樣,並且沒有太多其他可能意味着它。

第一個版本(帶有_pod后綴的版本)仍然有效,但沒有給出錯誤,因為對於z值,選擇了默認值int(0)。 對於const int引用的同義詞。

在第二個版本中,如果不為其賦值,則無法定義const引用。 編譯器會給您一個錯誤,因為以后您無法為其分配任何值。

另外,你正在使用的編譯器在這里起着重要作用,也許這是一個bug,只是因為你在int成員之前聲明了一個類成員。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

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