簡體   English   中英

指向成員的指針標准轉換序列

[英]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的指針。 並且因為D1B未命名的子對象, ptr可以指向該子對象a成員; 實際上, ptr指向D1::B::a ,其中B::aD1B子對象的成員。 結果指針ptr指的是子對象B (不是D1 )的同一個成員,實際上是D1::B::a 那是因為即使D1有一個名為a的實際非繼承成員,結果指針仍然引用該子對象的同一成員。

#2-這里,初始化器是一個純右值,指定指向成員D2::a的指針。 並且因為D2 (如D1 )有B未命名的子對象, ptr可以指向該子對象a成員; 實際上, ptr指向D1::B::a ,其中B::aD1B子對象的成員。 結果指針ptr指的是子對象B (不是D1 )的同一個成員,實際上是D1::B::a那是因為即使D1有一個實際的非繼承成員a ,結果指針仍然指的是同一個成員該子對象的成員。

#3-這里,初始化器是一個純右值,指定指向成員D1::a的指針。 並且因為D1B未命名的子對象, ptr可以指向該子對象a成員; 實際上, ptr指向D1::B::a ,其中B::aD1B子對象的成員。 結果指針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;

在這種情況下, ptr1ptr2每個都包含ab在它們各自類中的偏移量(在這兩種情況下通常都是 0)。

但是,當我們將 then 應用於D1的 object(它同時具有B1B2基類)時,編譯器必須首先應用一個偏移量以獲取基類 class 子對象,然后將第二個指針應用於成員偏移量以獲取該基礎子對象的成員。

您的困惑源於一種誤解,即&B::a&D2::a&D1::a是不同的。 事實上,它們是相同的; 它們都是類型為int B::*的純右值,都指定為&B::a 它們完全相同,因為11x01相同,或者給定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

所以:

  1. int D1::*ptr = &B::a; 可以通過隱式轉換(從int B::*int D1::*
  2. int D1::*ptr = &D2::a; 出於完全相同的原因是可以的,因為&D2::a只是&B::a的混淆名稱。
  3. int B::*ptr = &D1::a; 可以,因為&D1::a只是&B::a的混淆名稱。
  4. 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*p1p1->*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.

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