使用指向基类的指针调用类的虚拟成员函数当然是在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 ::*的任何理由.
简单的答案是,C++ 没有定义您正在尝试的转换,因此您的程序格式错误。
考虑标准转换(C++11§4/1):
标准转换是具有内置含义的隐式转换。第 4 条列举了所有此类转换。
由于您没有执行任何转换,也没有定义任何自定义转换,因此您确实在执行这样的标准转换。在不列举所有可能的此类转换的情况下,您的示例对两个显式感兴趣:指针转换和指向成员转换的指针。请注意,C++ 不将指向成员类型的指针视为指针类型的子集。
指向成员转换的指针在 C++11§4.11 中定义,并且恰好包含两个转换:
“指向 cv T 类型的 B 成员的指针”,其中 B 是类类型,可以转换为 [...] “指向类型 cv T 的 D 成员的指针”,其中 D 是派生类 [.. .] 的 B
void
(4.10/2) 的指针,允许将任何指针类型转换为指向void
.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 };
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-A
s 是编译器添加的隐藏成员。
当我们谈论常规指针时,这一切都很好。但是当我们有一个指向成员的指针时,我们没有任何对象可以从中获取隐藏的基指针。因此,如果我们只有B X::*
,则绝对没有办法将其转换为A X::*
没有实际的 X 对象。
虽然理论上可以允许这样的转换,但它会非常复杂。例如,指向成员的指针需要保存可变数量的数据(原始对象具有的所有隐藏的指向基值的指针)。
理论上,C++ 只允许将成员指针转换为非虚拟基类(在本例中D X::*
为B X::*
or C X::*
,但不是A X::*
)。或者至少我不明白为什么这对于实现来说是一个无法克服的问题。我猜想之所以没有这样做,是因为它会给标准带来额外的复杂性,但几乎没有什么好处。或者标准可能不想排除异常的继承实现。例如,一个实现可能希望使用隐藏的指向基类成员的指针来实现所有继承,就好像它始终是虚拟的一样(出于调试目的,或者为了与其他语言兼容,或者其他)。或者也许它只是被忽视了。也许在该标准的另一次修订中(2020 年?)
归档时间: |
|
查看次数: |
975 次 |
最近记录: |