我正在尝试将指向派生类的数据成员的指针强制转换为指向基类数据成员的指针,但以下代码无法编译:
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
它适用于我的环境,但这种行为是否得到保证?
答案 0 :(得分:3)
你不能拥有你想要的东西。只有在这种情况下,您才能获得数据成员的多态性:pointer to member of D of type T
已转换为pointer of member of B of type T
,其中B
是D
的基类。
不允许将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]
空指针常量(4.10)可以转换为指向成员类型的指针。 [...]
- 醇>
类型“指向cv T类型B的成员的指针”的prvalue,其中B是类类型,可以转换为类型为“指向成员的指针”的prvalue Cv T“类型的D,其中D是B的派生类(第10条)。[...]
因此,我们发现标准转化(隐式转化)不允许转化
§5.2.9静态演员[expr.static.cast]
- 类型“指向cv1 T类型D的成员的指针”的prvalue可以转换为类型为cv2 T的“指向B成员的指针”的prvalue, 其中B是D的基类(第10条),如果是有效标准 从“指向T类型B的成员的指针”转换为“指向的指针” 类型T的D成员“存在(4.11),并且cv2是相同的 cv-qualification为,或者比cv1更高的cv资格。 [...]
醇>
对于static_cast
,我们看到基本上只允许指向成员强制转换的指针进行标准转换。您观察到的是static_cast
是编译器错误。
§5.2.10重新解释cast [expr.reinterpret.cast]
类型为“指向T1类型X的成员的指针”的prvalue可以是 显式转换为不同类型的prvalue“指向 如果T1和T2都是函数类型或两者兼有,则为T2类型Y的成员 对象类型。 [...]。此转换的结果未指定, 除以下情况外:
(10.1)将“指向成员函数的指针”类型的prvalue转换为a 指向成员函数类型的不同指针并返回其原始状态 type产生指向成员值的原始指针。
(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
。