[英]C++ Using a reference to the variable being defined
以下代碼是否有效C ++,根據標准(折扣... s)?
bool f(T& r)
{
if(...)
{
r = ...;
return true;
}
return false;
}
T x = (f(x) ? x : T());
眾所周知,在這個項目使用的GCC版本中編譯(4.1.2和3.2.3 ......甚至沒有讓我開始...),但應該嗎?
編輯 :我添加了一些細節,例如f()概念在原始代碼中的樣子。 基本上,它意味着在某些條件下初始化x 。
從語法上來說,如果你試試這個
#include <iostream>
using namespace std;
typedef int T;
bool f(T& x)
{
return true;
}
int main()
{
T x = (f(x) ? x : T());
cout << x;
}
它輸出一些隨機垃圾。 但是,如果你修改
bool f(T& x)
{
x = 10;
return true;
}
然后它輸出10.在第一種情況下,聲明對象x
,並且編譯器分配一些偽任意值(所以你不初始化它),而在第二種情況下你專門分配一個值( T()
,即0
聲明后,即你初始化它。
我認為你的問題類似於這個問題: 在初始化中使用新聲明的變量(int x = x + 1)?
它無疑應該編譯,但可能有條件地導致未定義的行為。
T
是非基本類型,則指定未定義的行為。 T
是基本類型,則定義良好的行為(如果它是非本地的),如果未在讀取之前分配,則定義為未定義的行為(除了字符類型,定義它以給出未指定的值)。 標准的相關部分是3.8規則,對象生命周期:
類型
T
對象的生命周期始於:
- 獲得具有適當對齊和T型尺寸的存儲,並且
- 如果對象具有非平凡的初始化,則其初始化完成。
所以x
的生命周期還沒有開始。 在同一節中,我們找到了使用x
控制的規則:
類似地, 在對象的生命周期開始之前但在對象將占用的存儲已經被分配之后,或者在對象的生命周期結束之后並且在對象占用的存儲被重用或釋放之前, 任何引用的glvalue之前可以使用原始對象,但僅限於有限的方式 。 對於正在建造或銷毀的物體,見12.7。 否則, 這樣的glvalue指的是已分配的存儲(3.7.4.2),並且使用不依賴於其值的glvalue的屬性是明確定義的。 如果出現以下情況,該程
- 左值到右值的轉換(4.1)應用於這樣的glvalue,
- glvalue用於訪問非靜態數據成員或調用對象的非靜態成員函數 ,或
- glvalue綁定到對虛基類(8.5.3)的引用,或
- glvalue用作dynamic_cast(5.2.7)的操作數或typeid的操作數。
如果你的類型是非原始的,那么嘗試分配它實際上是對T::operator=
的調用,這是一個非靜態成員函數。 完全停止,根據案例2,這是未定義的行為。
在不調用成員函數的情況下分配原始類型,現在讓我們仔細看看4.1節,左值到右值的轉換,以確定何時該左值到右值的轉換將是未定義的行為:
當在未評估的操作數或其子表達式中發生左值到右值轉換時(第5條),不訪問引用對象中包含的值。 在所有其他情況下,轉換結果根據以下規則確定:
- 如果
T
是(可能是cv限定的 )std::nullptr_t
,則結果是空指針常量(4.10)。- 否則,如果
T
具有類類型,則轉換復制 - 從glvalue初始化類型T
的臨時值,並且轉換的結果是臨時的prvalue。- 否則,如果glvalue引用的對象包含無效指針值(3.7.4.2,3.7.4.3),則行為是實現定義的。
- 否則,如果
T
是(可能是cv限定的 )無符號字符類型(3.9.1),並且glvalue引用的對象包含不確定的值(5.3.4,8.5,12.6.2),並且該對象不有自動存儲持續時間或glvalue是一元&
運算符的操作數或它綁定到引用,結果是一個未指定的值。- 否則,如果glvalue引用的對象包含不確定的值,則行為未定義。
- 否則,glvalue指示的對象中包含的值是prvalue結果。
(請注意,這些規則反映了即將推出的C ++ 14標准的重寫,以使它們更容易理解,但我認為這里的行為沒有實際的變化)
在進行左值引用並將其傳遞給f()
變量x
具有1個不確定值。 只要該變量具有基本類型並且在讀取之前分配其值(讀取是左值到右值的轉換),代碼就可以了。
如果在讀取之前未分配變量,則效果取決於T
字符類型將導致代碼執行並使用任意但合法的字符值。 所有其他類型都會導致未定義的行為
1除非x
具有靜態存儲持續時間,例如全局變量。 在這種情況下,根據第3.6.2節非局部變量的初始化,它在執行前是零初始化的:
具有靜態存儲持續時間(3.7.1)或線程存儲持續時間(3.7.2)的變量應在任何其他初始化發生之前進行零初始化(8.5)。
在這種靜態存儲持續時間的情況下,不可能遇到未指定值的左值到右值轉換。 但是零初始化對於所有類型都不是有效狀態,所以仍然要小心。
雖然范圍起着重要作用,但真正的問題是關於對象的生命周期,更確切地說,對於具有非平凡初始化的對象,生命周期開始時。
這與初始化表達式使用變量本身密切相關嗎? 並將C ++對象傳遞給自己的構造函數合法嗎? 。 雖然我對這些問題的回答並沒有巧妙地回答這個問題,但它似乎並不重復。
我們關注的C ++標准草案的關鍵部分是3.8
節[basic.life] ,它說:
對象的生命周期是對象的運行時屬性。 如果一個對象屬於類或聚合類型,並且它或其成員之一由除了普通默認構造函數之外的構造函數初始化,則稱該對象具有非平凡的初始化 。 [注意:通過簡單的復制/移動構造函數進行初始化是非平凡的初始化。 - 結束注釋] 類型T對象的生命周期從以下開始 :
- 獲得具有適當對齊和T型尺寸的存儲,並且
- 如果對象具有非平凡的初始化,則其初始化完成。
所以在這種情況下我們滿足第一個子彈,已經獲得了存儲。
第二個子彈是我們遇到麻煩的地方:
非平凡的初始化案例
我們可以從缺陷報告363中得到一個基本推理,它要求:
如果是這樣,UDT的自我初始化的語義是什么? 例如
#include <stdio.h> struct A { A() { printf("A::A() %p\\n", this); } A(const A& a) { printf("A::A(const A&) %p %p\\n", this, &a); } ~A() { printf("A::~A() %p\\n", this); } }; int main() { A a=a; }
可以編譯和打印:
A::A(const A&) 0253FDD8 0253FDD8 A::~A() 0253FDD8
擬議的決議是:
3.8 [basic.life]第6段表明此處的引用是有效的。 允許在完全初始化之前獲取類對象的地址,並且只要引用可以直接綁定,就允許將其作為參數傳遞給引用參數。 [...]
因此,在對象的生命周期開始之前,我們在對象的使用方面受到限制。 我們可以從缺陷報告中看到綁定對x
的引用是有效的,只要它直接綁定即可。
我們可以做的事情在第3.8
節( 缺陷報告引用的相同部分和段落 )中說明( 強調我的 ):
類似地,在對象的生命周期開始之前但在對象將占用的存儲已經被分配之后,或者在對象的生命周期結束之后並且在對象占用的存儲被重用或釋放之前,任何引用的glvalue之前可以使用原始對象,但僅限於有限的方式。 對於正在建造或銷毀的物體,見12.7。 否則,這樣的glvalue指的是已分配的存儲(3.7.4.2),並且使用不依賴於其值的glvalue的屬性是明確定義的。 如果出現以下情況,該程
左值到右值的轉換(4.1)應用於這樣的glvalue,
glvalue用於訪問非靜態數據成員或調用對象的非靜態成員函數,或
glvalue綁定到對虛基類(8.5.3)的引用,或
glvalue用作dynamic_cast(5.2.7)的操作數或typeid的操作數。
在您的情況下,我們在這里訪問非靜態數據成員,請參閱上面的重點:
r = ...;
因此,如果T
具有非平凡的初始化,則該行調用未定義的行為,因此從r
讀取也將是訪問,在缺陷報告1531中涵蓋。
如果x
具有靜態存儲持續時間,則它將被初始化為零,但據我所知,由於在動態初始化期間將調用構造函數, 因此初始化已完成 。
瑣碎的初始化案例
如果T
具有微不足道的初始化,則一旦獲得存儲,則生命周期開始,並且寫入r
是明確定義的行為。 雖然注意在初始化之前讀取r
將調用未定義的行為, 因為它會產生不確定的值 。 如果x
具有靜態存儲持續時間,那么它是零初始化的,我們沒有這個問題。
它是否應該編譯,在任何一種情況下,無論你是否正在調用未定義的行為,這都允許編譯。 盡管可能,編譯器沒有義務為未定義的行為生成診斷。 它只有義務對不良形式的代碼進行診斷,這里沒有任何麻煩的情況。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.