[英]Pointer-to-member standard conversion sequence
我過去總是在cppinsights中復制和粘貼我的代碼,以查看我的代碼中哪里有對話是由編譯器隱式執行的。 但是此站點上未顯示指針到成員的標准轉換。
當看到這個問題的答案時,我變得更加困惑: Downcasting pointer to member leads to undefined behavior 。 我認為這個問題本身是錯誤的,因為據我所知,根據[conv.mem]/2可以安全地向下轉換指向成員的指針; 但除非應用[expr.static.cast]/12 ,否則不能安全地向上轉換。 這個問題有以下代碼(已編輯):
struct B { int a; };
struct D1 : B {};
struct D2 : B { int b; };
void f()
{
int D1::*ptr = &B::a; // #1
int D1::*ptr = &D2::a; // #2
int B::*ptr = &D1::a; // #3
int D1::*ptr = &D2::b; // #4
}
我的困惑只是因為我不知道ptr
實際指向什么? 給封閉的 object 本身的成員? 或封閉 object 的子對象的成員?
因此,在這里發布任何問題之前,我求助於一位 C++ 程序員(實際上是我的退休老師)幫助我解釋上述隱含的對話(如果有的話)。 簡而言之,這就是我得到的:
#1-這里,初始化器是一個純右值,指定指向成員B::a
的指針。 並且因為D1
有B
未命名的子對象, ptr
可以指向該子對象a
成員; 實際上, ptr
指向D1::B::a
,其中B::a
是D1
的B
子對象的成員。 結果指針ptr
指的是子對象B
(不是D1
)的同一個成員,實際上是D1::B::a
; 那是因為即使D1
有一個名為a
的實際非繼承成員,結果指針仍然引用該子對象的同一成員。
#2-這里,初始化器是一個純右值,指定指向成員D2::a
的指針。 並且因為D2
(如D1
)有B
未命名的子對象, ptr
可以指向該子對象a
成員; 實際上, ptr
指向D1::B::a
,其中B::a
是D1
的B
子對象的成員。 結果指針ptr
指的是子對象B
(不是D1
)的同一個成員,實際上是D1::B::a
那是因為即使D1
有一個實際的非繼承成員a
,結果指針仍然指的是同一個成員該子對象的成員。
#3-這里,初始化器是一個純右值,指定指向成員D1::a
的指針。 並且因為D1
有B
未命名的子對象, ptr
可以指向該子對象a
成員; 實際上, ptr
指向D1::B::a
,其中B::a
是D1
的B
子對象的成員。 結果指針ptr
指的是子對象B
(不是D1
)的同一個成員,實際上是D1::B::a
; 那是因為即使D1
有一個名為a
的實際非繼承成員,結果指針仍然引用該子對象的同一成員。
#4-這里,初始化器是一個純右值,指定指向成員D2::b
的指針。 並且因為D2
沒有D1
子對象可以ptr
指針指向它的D1::a
成員,即使使用static_cast
程序也需要這種轉換是錯誤的。
實際上,我覺得這個解釋是合乎邏輯的並且讓我滿意,但我認為它有一些錯誤並且不是100%正確,具體來說,第3段。
我的問題:
如果段落有錯誤,請隨時更正,如果可能,請用更正后的段落作答。 謝謝,很抱歉拖了這么久。
我建議將指向成員的指針視為偏移量,而不是真正的指針。
因此,指向成員的指針告訴您使用什么偏移量來獲取對象中的特定成員——但要取消引用它,您必須將指向成員的指針與適當類型的 object 的基地址組合起來。
這意味着指向成員的指針本身並沒有真正指向任何東西。 它(至少通常)指定一個調整/偏移量,可以應用於實際 object 的地址以到達該 object 的指定成員。
您的前三個問題中的大部分實際上都歸結為如何完成名稱查找。 當您提供成員的名稱時,編譯器會查找該名稱,並找出該名稱所指的 object 的哪一部分。 指向成員的指針持有到達該 object 的實例成員或從該實例派生的任何 object 的成員所需的任何偏移量(在一種情況下會變得棘手(我將在下面介紹,但你沒有問)。
因此,在前三種情況下,在 B、D1 或 D2 中的任何一個中,只有一個名為a
的成員。 因此,當查找名稱a
時,它將引用作為基 class 的成員的a
,無論您如何指定它,並且指向成員的指針將保持相同的偏移量。
你的第四個案例根本不應該像現在這樣編譯。 使用 static 轉換,您可以編譯它,但使用它的唯一方法是將它轉換回D2::*
(然后將其應用於D2
類型的 object 或從它派生的東西)。
上面我提到了一個你沒有涉及的有趣案例。 那是你涉及多個 inheritance 的時候。 在這種情況下,當您引用基類 class object 的成員時,編譯器可能需要應用兩個不同的偏移量。
struct B1 { int a; };
struct B2 { int b; };
struct D1 : public B1, public B2 {};
int B1::*ptr1 = &B1::a;
int B2::*ptr2 = &B2::b;
D1 d;
d.*ptr1 = 1;
d.*ptr2 = 2;
在這種情況下, ptr1
和ptr2
每個都包含a
和b
在它們各自類中的偏移量(在這兩種情況下通常都是 0)。
但是,當我們將 then 應用於D1
的 object(它同時具有B1
和B2
基類)時,編譯器必須首先應用一個偏移量以獲取基類 class 子對象,然后將第二個指針應用於成員偏移量以獲取該基礎子對象的成員。
您的困惑源於一種誤解,即&B::a
、 &D2::a
和&D1::a
是不同的。 事實上,它們是相同的; 它們都是類型為int B::*
的純右值,都指定為&B::a
。 它們完全相同,因為1
與1x
和01
相同,或者給定inline namespace ns { int j; }
inline namespace ns { int j; }
, &ns::j
等同於&j
。
您可以通過編譯一個簡單的程序來觀察這一點:
template<auto> int g();
int f1() { return g<&B::a>(); } // calls int g<&B::a>()
int f2() { return g<&D2::a>(); } // calls int g<&B::a>()
int f3() { return g<&D1::a>(); } // calls int g<&B::a>()
理解指向(數據)成員的指針的最佳方式是function (在基礎意義上); TM::*
類型的指針(例如)將M
類型的泛左值映射到T
類型的泛左值。 在簡單的情況下,指定的數據成員位於 class 內的固定偏移量處,PTDM 可以實現為字節偏移量(在offsetof
的意義上),但在更復雜的情況下(涉及虛擬繼承),它可能需要查閱 vtable。
現在,為什么類型為int B::*
的&B::a
可以隱式轉換為int D1::*
? 答案是指針D1*
可隱式轉換為B*
(通過派生到基轉換,因為D1
具有類型B
的明確可訪問基),並且指向數據成員的指針與其 class 類型是逆變的。 換句話說,給定一個從B
類型的泛左值到int
類型的泛左值的function ,我們總是可以從D1
類型的泛左值到int
類型的泛左值構造一個 function,方法是在派生到基礎的轉換前加上: (D1 → B) ∘ (B → int)
⇒ D1 → int
。
所以:
int D1::*ptr = &B::a;
可以通過隱式轉換(從int B::*
到int D1::*
。int D1::*ptr = &D2::a;
出於完全相同的原因是可以的,因為&D2::a
只是&B::a
的混淆名稱。int B::*ptr = &D1::a;
可以,因為&D1::a
只是&B::a
的混淆名稱。int D1::*ptr = &D2::b;
失敗,因為&D2::b
確實是int D2::*
類型,而不是int B::*
類型。 您可以通過int B::*
強制轉換 #4 進行編譯; 您可以顯式向下轉換指向數據成員的指針,原因與您可以顯式向上轉換指向 class 的指針的原因相同:您告訴編譯器相信您,這個指向派生 class 的數據成員的指針實際上是一個轉換后的指向數據成員的指針基數 class 的。由於允許在B*
類型的指針上顯式向上轉換static_cast<D2*>
,因此將向下轉換的指針應用於數據成員的效果是將實例指針從B*
向上轉換為D2*
,然后訪問D2::b
。
也就是說,給定類型為D1*
的p1
, p1->*static_cast<int D1::*>(static_cast<int B::*>(&D2::b))
等同於static_cast<D2*>(static_cast<B*>(p))->*(&D2::b)
,即static_cast<D2*>(static_cast<B*>(p))->b
,同樣有未定義的行為。
最后,為什么編譯器決定&D1::a
應該是int B::*
而不是int D1::*
類型? 這是幫您一個忙的語言; 指向基 class 數據成員的指針比指向派生 class 數據成員的指針更強大,因為它可以應用於任何派生 class(具有可訪問的明確基)。 您想要轉換的唯一情況是您可以訪問基數 class 並且您想將指針指向不能訪問的人,即私有或受保護的 inheritance。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.