我遇到了一个问题,即派生到派生类会解决问题。我在S.O上找到了一个答案,说它可以导致UB,测试它,它编译和正常工作。是不确定的行为?如果这是解决这个问题的正确方法吗?
class A
{
public:
A(){};
~A(){}
};
class B : public A
{
public:
B(){};
~B(){}
void Show() { std::cout << "Show" << std::endl; }
};
int _tmain(int argc, _TCHAR* argv[])
{
A a;
B* b = static_cast<B*>(&a);
b->Show();
return 0;
}
答案 0 :(得分:8)
只要指向基类型的指针实际上指向派生类型的实例,那么根据C ++标准,这种用法不是未定义的。 但 ,在您的代码示例中,指针b
未指向B
的实例或其任何派生类型(有无),它指向A
的实例。所以你的代码实际上会调用未定义的行为。
我在S.O上找到了答案,说它可以导致UB,测试它, 它编译和正常工作。
某些代码编译和正常工作的事实并不排除代码调用未定义行为的可能性,因为未定义的行为包括“似乎工作”。您应该避免未定义行为的原因是因为无法保证它在下次调用UB时的工作方式相同。
是不确定的行为?如果这是一个正确的方法 这个问题?
在您的示例中,是的,它是未定义的行为。正确的方法取决于您的代码实际应该做什么,因为您提供的示例充其量只是一个学术范例。
为清楚起见,对main()
函数的以下修改具有明确定义的行为,并且C ++标准明确允许:
B objectB;
A* ptrA = &objectB;
B* b = static_cast<B*>(ptrA);
b->Show();
这里定义得很好,因为指针ptrA
实际指向B
的实例,即使指针本身具有类型A*
。在上面的示例中,从A*
转换为B*
然后在转换指针上调用B
的一个函数将起作用。不同之处在于,在您的问题的示例中,b
实际上并未指向B
的实例。
相关条款(强调我的):
“指向 cv1
B
的指针”的右值,其中B
是类类型,可以是 转换为“指向 cv2D
的指针”的右值,其中D
是一个类 来自B
的派生(第10条),如果是有效的标准转换 “指向D
”指向“指向B
”的指针(4.10), cv2 是相同的 cv - 限定为 cv - 资格, cv1 和B
是 不是D
的虚拟基类。空指针值(4.10)是 转换为目标类型的空指针值。 如果 “指向cv1B
的指针”类型的右值指向实际为的B
类型为D
的对象的子对象,结果指针指向 包含D
类型的对象。否则,演员的结果是 未定义。强>
答案 1 :(得分:2)
您可以将指向实际指向派生实例的基础对象的指针转换为指向派生对象的指针。
在您的代码中,&a
指向的对象不是派生对象,而是基础对象,您正在做的事情确实是未定义的行为。
在实现中,我知道如果类没有虚函数或基础并且派生对象不添加任何数据成员而只是方法,它应该“工作”。仍然没有正式保证工作。
请不要这样做。
答案 2 :(得分:1)
如果指向的对象确实是B
,则允许强制转换(因此可以工作)。在您的示例中,它是一个普通的A
,因此,您会得到未定义的行为。
这提出了问题,为什么会有效?它工作(或似乎工作),因为您调用的方法不访问该对象。如果将一些成员变量添加到B
并尝试在Show()
中访问它们或者使Show
成为虚函数,则更有可能失败。但无论如何它是UB,所以基本上任何事情都可能发生。