簡體   English   中英

在成員初始化程序中創建臨時 object 時銷毀它的要點

[英]The point of destroying a temporary object when it created in a member-initializer

#include <iostream>
struct A{
    A(int){
       
    }
    ~A(){
        std::cout<<"A destroy\n";
    }
};
struct B{
    B(int){
        std::cout<<"B construct\n";
    }
    ~B(){
        std::cout<<"B destroy\n";
    }
};
struct Content{
    A const& a;
};
struct Data{
    Data():c{0},b{0}{

    }
    Content c;
    B b;
};
int main(){
    Data d;
    std::cout<<"exit\n";
}

GCC 的 output 是:

B construct
A destroy
exit
B destroy

Clang 抱怨此代碼格式錯誤。 是兩個編譯器的性能。

關於Clang報錯,標准中確實有相關規定,即:
[class.init#class.base.init-8]

綁定到 mem-initializer 中的引用成員的臨時表達式格式不正確。

我不確定 Clang 是否理解過度了? 在我看來,規則似乎是說,由mem-initializer 的 mem-initializer-id命名的引用成員不應綁定到臨時表達式。 在我的示例中,class Data的成員c不是參考。

據推測, Clang認為任何使引用成員綁定到臨時表達式的引用成員的初始化都發生在成員初始化器中,都是格式錯誤的。 所以我舉了一個例子來檢驗Clang是否這么認為。

struct A{
  int const& rf;
};
struct B{
   B():a(new A{0}){}
   A* a;
};
int main(){
  B b;
  delete b.a;
}

給出警告但不是錯誤。 所以,我不確定Clang是否這么認為。 我不知道它是如何理解規則的?

如果第一個例子本身是有效的,我會認為GCC不符合標准。 因為臨時銷毀object的順序。

[class.temporary#4]

臨時對象被銷毀作為評估完整表達式([intro.execution])的最后一步,該完整表達式(詞法上)包含它們的創建點。

臨時 object 將在成員初始化程序的完整表達式末尾被銷毀,在我的示例中,即c{0} 但是, GCC在子對象b構建后會破壞臨時 object。 我認為這是第一個問題。

實際上,臨時綁定的引用並不是這里列出的例外:
[class.temporary#6]

引用綁定到的臨時 object 或臨時 object 是引用綁定到的子對象的完整 object 在引用的生命周期內持續存在
此生命周期規則的例外情況是:

  • 臨時 object 綁定到 function 調用 ([expr.call]) 中的引用參數,直到包含調用的完整表達式完成。
  • 一個臨時的 object 綁定到從括號中的表達式列表 ([dcl.init]) 初始化的 class 類型的聚合的引用元素,直到完成包含表達式列表的完整表達式。
  • 臨時綁定到 function 返回語句 ([stmt.return]) 中的返回值的生命周期不會延長; 臨時在 return 語句中的完整表達式的末尾被銷毀。
  • 臨時綁定到 new-initializer ([expr.new]) 中的引用,直到包含 new-initializer 的完整表達式完成為止。

也不是,也就是說,我的第一個例子不是上面列表中列出的例外。 因此,臨時 object 的生命周期應該與b b的子對象c的子對象a的生命周期相同,它們都具有相同的生命周期。 那么,為什么GCC這么早就毀掉臨時的object呢? 臨時 object 不應該與main中的 object b一起銷毀嗎? 我想這是GCC的第二期。 我不知道Clang是如何處理臨時 object 的,因為它之前給出了錯誤。

問題:

  1. 那是Clang報告第一個示例的錯誤嗎? 如果是正確的,[class.init#class.base.init-8] 是否應該更清楚?

  2. 如果Clang過度理解[class.init#class.base.init-8],那么GCC破壞臨時object的性能是否被認為是bug? 或者, exceptions遺漏了這種情況? 即使異常省略了這種情況(引用綁定發生在成員初始化程序中),我仍然認為GCC有錯誤,臨時不應該在完整表達式( c{0} )結束時被銷毀,這是在構造b之前排序的.

如何解讀以上這些問題?

Clang 是正確的,但是是的,標准可能更清晰。

[class.temporary]/6 (通過引用綁定的臨時表達式生命周期延長)的作用是確保除了列出的異常之外,綁定到引用的臨時對象的生命周期延長到引用的生命周期。 但是,當引用是 class 非靜態數據成員時,引用的生命周期在綁定點(這發生在(可能是默認的)構造函數中)靜態未知,因此臨時可以延長壽命。 由於非靜態數據成員不包含在異常列表中,因此必須通過其他方式防止這種情況發生,實際上是通過[class.base.init]中的 IF 案例:

8 - 綁定到 mem-initializer 中的引用成員的臨時表達式格式不正確。

11 - 從默認成員初始化程序綁定到引用成員的臨時表達式格式不正確。

我們必須得出結論,這種語言的意圖是呈現格式錯誤的任何嘗試,即從類的(可能是默認的)構造函數中將臨時綁定到 class 數據成員,因為否則臨時將有資格延長生命周期,這將是荒謬的(其中引用的生命周期是靜態未知的)。 因此,這必須包括子聚合的參考成員; 標准中包含一個說明這一點的說明是合適的。

值得考慮將臨時表達式綁定到引用屬於 3 種情況:

  1. IF 在 [class.base.init]/8 和 /11 下(類非靜態數據成員)
  2. [class.temporary]/6 下的例外情況; 臨時在完整表達式結束時被銷毀。
  3. 否則,會發生壽命延長。

因此,如果編譯器不拒絕,並且代碼不屬於 [class.temporary]/6 中的異常之一,並且編譯器不執行生命周期擴展(到引用的完整生命周期),則編譯器是有過錯。

MSVC 也錯誤地接受您的代碼和輸出:

main
A(int)
~A()
B(int)
D()
exit
~B()

一個有趣的情況是,當包含參考非靜態數據成員的 class 是一個聚合,因此有資格通過列表初始化語法進行聚合初始化(示例改編自CWG 1815 ):

struct A {};
struct C { A&& a = A{}; };
C c1;         // #1
C c2{A{}};    // #2
C c3{};       // #3
C c4 = C();   // #4

這里#1#4格式不正確,盡管 gcc 和 MSVC 錯誤地接受。 #3根據 [class.base.init]/11 標准的當前措辭格式錯誤,但這與CWG 1815中指出的委員會的意圖相反:

2014 年 2 月會議記錄:

CWG 同意建議的方向,將示例中的 #3 視為 #2 並刪除默認構造函數

也就是說, #3將是有效的,並導致壽命延長; gcc 和 clang 這樣做,但 MSVC 無法在#2#3中執行壽命延長; icc 僅對#2執行壽命延長 奇怪的是,clang 報告了一個診斷,聲稱盡管這樣做了,但它不會執行壽命延長!

警告:抱歉,不支持使用默認成員初始化程序聚合初始化創建的臨時的生命周期延長; 臨時的生命周期將在完整表達式結束時結束 [-Wdangling]

在聚合初始化中詢問了 mem-initializer 的有效性和/或壽命延長

請注意(假設這是允許的)它僅適用於c3是完整的 object; 如果它是成員c (如您的第一個示例中的 c )[class.base.init] 將明確適用,並且c3也將是不正確的。

暫無
暫無

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

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