簡體   English   中英

無法將“指向派生類的成員指針”轉換為“指向基類的成員指針”

[英]Cannot cast “member pointer to derived class” to “member pointer to base class”

使用指向基類的指針調用類的虛成員函數當然是 C++ 中非常常見的事情。 所以我覺得很奇怪,當你有一個成員指針而不是一個普通的指針時,似乎不可能做同樣的事情。 請考慮以下代碼:

struct B
{
    virtual void f();
};

struct D : B
{
    virtual void f();
};

struct E
{
    B b;
    D d;
};

int main()
{
    E e;

    // First with normal pointers:
    B* pb1 = &e.b;  // OK
    B* pb2 = &e.d;  // OK, B is a base of D
    pb1->f();  // OK, calls B::f()
    pb2->f();  // OK, calls D::f()

    // Now with member pointers:
    B E::* pmb1 = &E::b;  // OK
    B E::* pmb2 = &E::d;  // Error: invalid conversion from ‘D E::*’ to ‘B E::*’
    (e.*pmb1).f();  // OK, calls B::f()
    (e.*pmb2).f();  // Why not call D::f() ???

    return 0;
}

Visual C++ 繼續說:

error C2440: 'initializing' : cannot convert from 'DE::* ' to 'BE::* ' Types pointed to are unrelated; conversion requires reinterpret_cast, C-style cast or function-style cast

我不明白為什么這些是“無關的”。 為什么這是不可能的?


編輯:
我試圖保持這個 C++ 問題,而不是關於我試圖解決的特定問題,但這基本上是我想要做的:

std::vector<B E::*> v;
v.push_back( &E::b ); // OK
v.push_back( &E::d ); // Error

B& g( E& e, int i )
{
  return e.*v[i];
}

E 是一個包含多個從 B 派生的成員的類。向量 v 用於組織(例如重新排序)指向這些成員的成員指針。 向量 v 很少變化。 函數 g() 允許您使用 v 中的索引來選擇 E 的成員之一。它經常被調用,並且每次都使用不同的 E。

如果你仔細想想,v 只是一個偏移量查找表。 函數 g() 只是選擇這些偏移量之一並將其添加到 E* 以返回 B*。 函數 g() 由編譯器內聯並編譯為僅 4 個 CPU 指令,這正是我想要的:

// g( e, 1 )
mov         rax,qword ptr [v (013F7F5798h)]
movsxd      rcx,dword ptr [rax+4]
lea         rax,[e]
add         rcx,rax

我想不出為什么標准不允許將 DE::* 轉換為 BE::* 的任何原因。

簡單的答案是,C++ 沒有定義您正在嘗試的轉換,因此您的程序格式錯誤。

考慮標准轉換(C++11§4/1):

標准轉換是具有內置含義的隱式轉換。 第 4 條列舉了所有此類轉換。

由於您沒有執行任何轉換,也沒有定義任何自定義轉換,因此您確實在執行這樣的標准轉換。 在不列舉所有可能的此類轉換的情況下,您的示例對兩個顯式感興趣:指針轉換和指向成員轉換的指針 請注意,C++ 不將指向成員類型的指針視為指針類型的子集。

指向成員轉換的指​​針在 C++11§4.11 中定義,並且恰好包含兩個轉換:

  • 空成員指針轉換,允許將空指針常量轉換為指向成員類型的指針 (4.11/1)。
  • 稍微做作的第二次轉換(4.11/2):

    “指向 cv T 類型的 B 成員的指針”,其中 B 是類類型,可以轉換為 [...] “指向類型 cv T 的 D 成員的指針”,其中 D 是派生類 [.. .] 的 B

將此與前一節 4.10 進行對比,后者定義了三種指針轉換:

  • 空指針轉換 (4.10/1) 允許將空指針常量轉換為指針類型。
  • 轉換為指向void (4.10/2) 的指針,它允許將任何指針類型轉換為指向void指針。
  • 最后為什么它使用指針(而不是指向成員的指針)(4.1​​0/3):

    A [...] “指向 cv D 的指針”,其中 D 是類類型,可以轉換為 [...] “指向 cv B 的指針”,其中 B 是 D 的基類 [...]

因此,總結一下:您嘗試的標准轉換是為您的指針示例定義的,但對於指向成員變量的指針根本不存在。

C++ 不允許這種轉換,許多其他人都喜歡它,因為它會使虛擬繼承的實現復雜化。

struct A { int a; };
struct B : virtual A { int b; };
struct C : virtual A { int c; };
struct D : B, C { int d };

以下是編譯器可能會嘗試布置這些類的方式:

A:  [ A::a ]
B:  [ B::b ] [ A ]
C:  [ C::c ] [ A ]
D:  [ D::d ] [ B ] [ C ] [ A ]

如果我們有一個指向 B 的指針,那么獲取指向其基類 A 的指針就不是一件容易的事,因為它與 B 對象的開頭沒有固定的偏移量。 A 可能就在 B::b 旁邊,也可能在其他地方,這取決於我們的對象是獨立的 B 還是作為 D 基礎的 B。沒有辦法知道我們有哪種情況!

要進行強制轉換,程序需要實際訪問 B 對象並從中獲取隱藏的基指針 所以真正的布局會更像這樣:

A:  [ A::a ]
B:  [ B::b | B::address-of-A ] [ A ]
C:  [ C::c | C::address-of-A ] [ A ]
D:  [ D::d | D::address-of-A ] [ B ] [ C ] [ A ]

其中address-of-A是編譯器添加的隱藏成員。

當我們談論常規指針時,這一切都很好。 但是當我們有一個指向成員的指針時,我們沒有任何對象可以去獲取隱藏的基指針。 因此,如果我們只有BX::* ,則在沒有實際 X 對象的情況下絕對無法將其轉換為AX::*

雖然理論上可以允許這樣的轉換,但它會非常復雜。 例如,指向成員的指針需要保存可變數量的數據(原始對象具有的所有隱藏的指向基址的指針值)。

理論上,C++ 只允許將成員指針轉換為非虛擬基類(在本例中, DX::*BX::*CX::* ,但不是AX::* )。 或者至少我不明白為什么這對於實現來說可能是一個無法解決的問題。 我想這沒有完成,因為它會給標准帶來額外的復雜性,而且收益很小。 或者也許標准不想排除不尋常的繼承實現。 例如,一個實現可能希望使用隱藏的指向基類成員的指針來實現所有繼承,就好像它始終是虛擬的(出於調試目的,或為了與其他語言的兼容性,或其他目的)。 或者它可能只是被忽視了。 也許在標准的另一個修訂版中(2020 年?)

暫無
暫無

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

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