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

rol*_*vax 6 c++ pointers

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

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
}
Run Code Online (Sandbox Code Playgroud)

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

auto bp = reinterpret_cast<Base Enclosing::*>(p); // passes compile
Run Code Online (Sandbox Code Playgroud)

我试着bp直截了当地使用:

Enclosing instance;
(instance.*bp).f(); // calls Base::f
Run Code Online (Sandbox Code Playgroud)

这不是我的预期,因为成员in Enclosing实际上是Derived类型.然后我尝试了这个:

(&(instance.*bp))->f(); // calls Derived::f
Run Code Online (Sandbox Code Playgroud)

它适用于我的环境,但这种行为有保证吗?

bol*_*lov 4

你不可能拥有你想要的东西。仅在这种情况下才能获得数据成员的多态性:pointer to member of D of type T转换为pointer of member of B of type TwhereB是 的基类D

\n\n

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

\n\n

为了实现多态性,你需要这样的东西:

\n\n
Enclosing e;\nDerived Enclosing::* d = &Enclosing::d;\n\nBase* b = &(e.*d);\nreturn b->foo();    // calls Derived::foo\n
Run Code Online (Sandbox Code Playgroud)\n\n
\n\n
\n

\xc2\xa74 标准转换 [conv]

\n\n

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

\n\n

\xc2\xa74.11 指向成员转换的指​​针 [conv.mem]

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

  2. \n
  3. \xe2\x80\x9c 类型的纯右值,指向 cv T\xe2\x80\x9d 类型的 B 成员的指针,其中 B 是类类型,可以转换为 \xe2\x80\x9c 类型的纯右值,指向成员\n类型为 cv T\xe2\x80\x9d 的 D 的,其中 D 是 B 的派生类(第 10 条)。 [...]

  4. \n
\n
\n\n

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

\n\n
\n

\xc2\xa75.2.9 静态转换 [expr.static.cast]

\n\n
    \n
  1. \xe2\x80\x9c 类型的纯右值,指向 cv1 T\xe2\x80\x9d 类型的 D 成员的指针,可以转换为 \xe2\x80\x9c 类型的纯右值,指向类型为 B\xe2\x80\x9d 的成员cv2 T,\n 其中 B 是 D 的基类(第 10 条),如果从 \xe2\x80\x9c 指针到 T\xe2\x80\x9d 类型的 B 成员的有效标准\n 转换为 \xe2\x80 \x9c 指向 T\xe2\x80\x9d 类型的 D 成员的指针存在 (4.11),并且 cv2 与 cv1 具有相同的 cv 限定,或比 cv1 更高。[...]
  2. \n
\n
\n\n

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

\n\n
\n

\xc2\xa7 5.2.10 重新解释强制转换 [expr.reinterpret.cast]

\n\n

\xe2\x80\x9c 类型的纯右值,指向 T1\xe2\x80\x9d 类型的 X 成员的指针,\n 可以显式转换为不同类型的纯右值 \xe2\x80\x9c 指向\n 类型的 Y 成员的指针T2\xe2\x80\x9d 如果 T1 和 T2 都是函数类型或都是对象类型。[...]。此转换的结果未指定,\n 除以下情况外:

\n\n

(10.1) 将\xe2\x80\x9c 类型的纯右值转换为成员函数\xe2\x80\x9d 到不同的指向成员函数类型的指针并返回到其原始类型,生成指向成员值的原始指针。

\n\n

(10.2) 将 \xe2\x80\x9c 类型的指向 T1\xe2\x80\x9d 类型的 X 的数据成员的指针转换为类型 \xe2\x80\x9c 指向 T2\xe2\ 类型的 Y 的数据成员的指针x80\x9d(其中 T2 的对齐要求并不比 T1 更严格)并且返回到其原始类型会产生指向成员值的原始指针。

\n
\n\n

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

\n\n

此外

\n\n
\n

\xc2\xa75.5 成员指针运算符 [expr.mptr.oper]

\n\n

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

\n
\n\n

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

\n