无法将"成员指针指向派生类"转换为"指向基类的成员指针"

Bar*_*ett 9 c++

使用指向基类的指针调用类的虚拟成员函数当然是在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;
}
Run Code Online (Sandbox Code Playgroud)

Visual C++继续说:

error C2440: 'initializing' : cannot convert from 'D E::* ' to 'B E::* ' 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];
}
Run Code Online (Sandbox Code Playgroud)

E是包含从B派生的若干成员的类.向量v用于组织(例如重新排序)到这些成员的成员指针.矢量v不经常变化.函数g()允许您使用索引选择E的一个成员到v.它经常被调用,每次都有不同的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
Run Code Online (Sandbox Code Playgroud)

我想不出为什么标准不允许将DE ::*转换为BE ::*的任何理由.

gha*_*.st 5

简单的答案是,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 的基类 [...]

因此,总而言之:您尝试的标准转换是为您的指针示例定义的,但对于指向成员变量的指针根本不存在。


n. *_* m. 5

C++ 不允许这种转换,许多其他语言也喜欢这种转换,因为这会使虚拟继承的实现变得复杂。

struct A { int a; };
struct B : virtual A { int b; };
struct C : virtual A { int c; };
struct D : B, C { int d };
Run Code Online (Sandbox Code Playgroud)

以下是编译器可能尝试布置这些类的方式:

A:  [ A::a ]
B:  [ B::b ] [ A ]
C:  [ C::c ] [ A ]
D:  [ D::d ] [ B ] [ C ] [ A ]
Run Code Online (Sandbox Code Playgroud)

如果我们有一个指向 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 ]
Run Code Online (Sandbox Code Playgroud)

在哪里address-of-As 是编译器添加的隐藏成员。

当我们谈论常规指针时,这一切都很好。但是当我们有一个指向成员的指针时,我们没有任何对象可以从中获取隐藏的基指针。因此,如果我们只有B X::*,则绝对没有办法将其转换为A X::*没有实际的 X 对象。

虽然理论上可以允许这样的转换,但它会非常复杂。例如,指向成员的指针需要保存可变数量的数据(原始对象具有的所有隐藏的指向基值的指针)。

理论上,C++ 只允许将成员指针转换为非虚拟基类(在本例中D X::*B X::*or C X::*,但不是A X::*)。或者至少我不明白为什么这对于实现来说是一个无法克服的问题。我猜想之所以没有这样做,是因为它会给标准带来额外的复杂性,但几乎没有什么好处。或者标准可能不想排除异常的继承实现。例如,一个实现可能希望使用隐藏的指向基类成员的指针来实现所有继承,就好像它始终是虚拟的一样(出于调试目的,或者为了与其他语言兼容,或者其他)。或者也许它只是被忽视了。也许在该标准的另一次修订中(2020 年?)