向上转换指向数据成员及其多态行为的指针

Upcasting a pointer to data member and its polymorphic behavior

本文关键字:指针 多态 转换 数据成员      更新时间:2023-10-16

我试图将指向派生类的数据成员的指针强制转换为指向基类的数据成员的指针,但以下代码无法编译:

class Base 
{
public:
virtual void f() {}
};
class Derived : public Base 
{
public:
void f() override {}
};
class Enclosing
{
public:
Derived member;
};
int main()
{
Derived Enclosing::*p = &Enclosing::member;
auto bp = static_cast<Base Enclosing::*>(p); // compile error
}

所以我改用了reinterpret_cast,代码编译:

auto bp = reinterpret_cast<Base Enclosing::*>(p); // passes compile

我试图直接使用bp

Enclosing instance;
(instance.*bp).f(); // calls Base::f

这不是我所期望的,因为Enclosing中的成员实际上是Derived类型。 然后我试了这个:

(&(instance.*bp))->f(); // calls Derived::f

它适用于我的环境,但这种行为是否得到保证?

你不能拥有你想要的。只有在以下情况下,您才能获得数据成员的多态性:pointer to member of D of type T转换为pointer of member of B of type T其中BD的基类。

您想要的,不允许将pointer to member of X of type T1转换为pointer to member of X of type T2或未定义的行为。

要实现多态性,您需要这样的东西:

Enclosing e;
Derived Enclosing::* d = &Enclosing::d;
Base* b = &(e.*d);
return b->foo();    // calls Derived::foo

§4 标准转换 [转换]

标准转换是具有内置含义的隐式转换。

§4.11 指向成员转换的指针 [conv.mem]

  1. 空指针常量 (4.10) 可以转换为指向成员类型的指针。[...]

  2. 类型为"指向 cv T 类型的 B 的成员的指针"的 prvalue,其中 B 是类类型,可以转换为类型为 "指向成员的指针" 的 prvalue cv T类型的D",其中D是B的派生类(第10条)。

因此,我们看到,对于标准转换(隐式转换),不允许进行转换

§5.2.9 静态强制转换

    类型为 "指向
  1. cv1 T 的 D 成员的指针"类型的 prvalue 可以转换为 cv2 T 类型的 "指向 B 的成员的指针" 类型的 prvalue, 其中 B 是 D 的基类(第 10 条),如果是有效的标准 从"指向类型 T 的 B 成员的指针"转换为"指向 存在 T" 的 D 成员 (4.11),并且 cv2 相同 CV资格为CV1,或比CV1更高的CV资格。[...]

对于static_cast我们看到基本上只允许指向成员转换的指针进行标准转换。这就是您观察到的,static_cast是编译器错误。

§ 5.2.10 重新解释演员表 [exr.reinterpret.cast]

类型为"指向类型 T1 的 X 的成员的指针"的 prvalue 可以是 显式转换为不同类型的 prvalue "指针指向 类型 T2 的 Y 的成员",如果 T1 和 T2 都是函数类型或两者兼而有之 对象类型。[...].此转换的结果未指定, 除以下情况外:

(10.1) 将"指向成员函数的指针"类型的 prvalue 转换为 指向成员函数类型的不同指针并返回到其原始状态 键入生成指向成员值的原始指针。

(10.2) 将 "指针"类型的 prvalue 转换为 X 的数据成员 类型 T1"到类型"指向类型 T2 的 Y 的数据成员的指针"(其中 T2的对准要求不比T1严格)和 返回到其原始类型将生成指向成员值的原始指针。

对于reinterpret_cast我们看到允许强制转换(如您所见,没有编译器错误。但是,结果未指定,除了上述 2 种情况,这意味着转换回原始值。它不适用于我们的情况,这意味着您的代码具有reinterpret_cast具有未定义的行为。

此外

§5.5 指针到成员运算符 [expr.mptr.oper]

将 pm-expression 缩写为 .*cast-expression 为 E1.*E2,E1 称为 对象表达式 。如果 E1 的动态类型不包含 E2 引用的成员,行为未定义。

这证明instance.*bp是有效的,因为bp指向的对象必须存在于实例中。这意味着bp的类型 .