假设我们有以下代码:
#include <iostream>
struct A
{
virtual void f() {
std::cout << "A::f()" << std::endl;
}
};
struct B: A
{
void f() {
std::cout << "B::f()" << std::endl;
}
};
void to_A(void* voidp) {
A* aptr = static_cast<A*>(voidp);
aptr->f();
}
void to_B(void* voidp) {
B* bptr2 = static_cast<B*>(voidp);
bptr2->f();
}
int main() {
B* bptr = new B;
void* voidp = bptr;
to_A(voidp); // prints B::f()
to_B(voidp); // prints B::f()
}
此代码是否保证总是在代码注释中工作,还是UB? AFAIK它应该没问题,但我想放心。
修改
好的,所以似乎已经达成共识,这个代码是UB,因为只能转换为确切的类型。那么,如果main()
更改为:
int main() {
B* bptr = new B;
to_A(static_cast<A*>(bptr)); // still prints B::f()
to_B(bptr); // still prints B::f()
}
还在UB吗?
答案 0 :(得分:3)
在这种特殊情况下,它可以工作,但还有其他情况,它不起作用。
问题是您将B指针转换为指向A指针的void指针的位置。 在这种情况下,指针将具有相同的值,但在以下条件中,这不再是真的:
唯一安全的方法是将它完全转换回与你来自的地方相同的类型。因此,如果将B指针强制转换为void-pointer,则将其强制转换为B指针,而不是指向另一个类的指针,即使它们属于同一继承树。
答案 1 :(得分:3)
您的第一个代码示例调用未定义的行为。
您可以使用static_cast
将指向对象类型的指针的标准转换反转为指向void
的指针,但只有在转换为void
的指针的值时才能保证结果返回原始对象类型是将指向原始类型的指针标准转换为指向void
的指针的结果。
您的第二个代码示例是正常的,因为您只能通过转换回转换的原始类型来反转从指针到类型到指针到void的转换。这在标准(C ++ 03)的5.2.9 [expr.static.cast]中得到保证。
...转换为“指向cv void的指针”并返回原始指针类型的对象的类型指针值将具有其原始值。
答案 2 :(得分:1)
这实际上是很常见的事情,特别是在需要C回调的函数中,所以必须要小心。典型的C API回调如下所示:
void pfnCallback( void * );
在您的C ++代码中,您决定使用基类来始终处理此特定回调,我们称之为
struct BaseCallback
{
virtual ~BaseCallback();
virtual call();
};
我们还编写了一个我们一直用于此C API的函数:
void OurCallback( void * var )
{
BaseCallback * ourBase = static_cast< BaseCallback * >)(var);
ourBase->call();
}
由于我们将始终从void *转换为BaseCallback *,因此当我们首先提供参数时必须小心,我们总是将BaseCallback *转换为void *。
答案 3 :(得分:-2)
我想,你没有正确使用铸造。我建议你从这里阅读前两个回复:
When should static_cast, dynamic_cast, const_cast and reinterpret_cast be used?
让我们先知道应该使用哪种演员阵容!
编辑:
关于你的问题,
此代码保证始终有效 在代码注释中还是UB? AFAIK它应该没问题,但我想 安慰。
如果你想将任何类型的指针传递给你的to_A()
函数,那么我认为static_cast
不是你应该使用的那个。您应该使用reinterpret_cast
。但是,如果您只想将指向B
(或A
的任何派生类)的指针传递给to_A()
函数,那么总是工作。如果通过“工作”表示它是否会返回非空值。
to_B()
函数的类似论据。我认为,将A*
传递给to_B()
可能会失败。这意味着,它可以简单地返回NULL,或者如果不为null,将从A::f()
调用to_B()
。
看到这个有趣的输出:http://www.ideone.com/2FZzw(打印 A :: f(),而不是 B :: f() !!!)