目前我正在使用传统的c ++代码库。在此代码库中,指向对象的指针将转换为void-pointers,然后存储在c-library中。 请考虑以下代码:
class interface {
public:
virtual void foo() {
std::cout << "Interface" << std::endl;}
virtual ~interface(){};
};
class debug_interface: public interface {
public:
virtual void foo() {
std::cout << "Debug Interface" << std::endl;}
};
对象interface
和debug_interface
在堆上分配,地址存储在void指针中。在某些时候,指针被检索,然后再转换回基类interface
。然后调用虚函数调用。参见
int main(int argc, char *argv[]){
void *handle = reinterpret_cast<void*>(new interface());
void *debug_handle = reinterpret_cast<void*>(new debug_interface());
//void *handle = new interface();
//void *debug_handle = new debug_interface();
interface *foo1 = reinterpret_cast<interface*>(handle);
interface *foo2 = reinterpret_cast<interface*>(debug_handle);
//interface *foo1 = static_cast<interface*>(handle);
//interface *foo2 = static_cast<interface*>(debug_handle);
foo1->foo();
foo2->foo();
return 0;
}
首先我不明白,为什么要使用reinterpret_cast。据我所知,指向对象的指针可以隐式转换为void *。此外,为了使这个演员显式化,static_cast就够了,对吧? 但更重要的问题是:将指针debug_handle强制转换为接口*(而不是debug_interface *)并调用虚拟调用是否真的可以保存?根据c ++标准,(5.2.10)这是未定义的行为:
指向对象的指针可以显式转换为指针 到另一个对象类型69。当prvalue v类型为“指针 将T1“转换为类型”指向cv T2的指针“,结果是 static_cast(static_cast(v))如果T1和T2都是 是标准布局类型(3.9)和对齐要求 T2并不比T1更严格。转换类型的prvalue “指向T1的指针”到“指向T2的指针”(其中T1和T2为 对象类型以及T2的对齐要求为否 比T1更严格,并恢复其原始类型的产量 原始指针值。任何其他此类指针的结果 转换未指定。
从handle
到foo1
的转换应该没问题,但我可以再次使用static_cast吗?
修改 我的示例源代码错误。 debug_interface是派生类的接口。
答案 0 :(得分:4)
免责声明: 第一部分是在两个接口未通过继承关联时编写的。
未定义的行为实际上发生在这里:
foo2->foo();
在这里,您在指向不实现此API的对象的指针上使用interface
API。
interface
和debug_interface
碰巧实现foo()
成员作为第一种方法的事实并没有改变任何东西:这些类与继承无关,因此它们不兼容。
您引用的摘录会处理允许转化本身的情况。在您的情况下,我的理解是您实际上可以将指向debug_interface
的指针转换为指向interface
的指针:然而,现在您可以使用指针指向接口的唯一安全方法是将其转换回来到debug_interface
指针:使用它来访问interface
成员是不安全的。
编辑:如果debug_interface
公开来自interface
,则这是一个不同的问题。
在这种情况下,从debug_interface*
转换为interface*
是完全安全的: derived-to-base 转换甚至可以由编译器隐式应用。
但为了安全起见,必须通过以下任一方式直接完成演员表
static_cast
dynamic_cast
(会引入运行时检查,对于向上播放来说是过度杀伤)。通过两个reinterpret_cast
执行它是一种未定义的行为:它可能适用于单继承(在某些编译器上),但标准绝对不能保证。
通过两个static_cast
执行它也将是一个未定义的行为。标准保证(强调我的):
指向对象的类型指针的值转换为“指向cv void的指针”并返回原始指针类型将具有其原始值。
在您的示例中,您不是转换回原始指针,而是转换为另一种指针类型:标准不会保证您将获得的值。
知道:
debug_interface
转换为interface
void *
然后返回指向同一对象类型的指针是安全的。您可以组装它以获得标准的保证解决方案:
// Safe, see point #1.
// The new expression returns a debug_interface* and static_cast applies a derived-to-base conversion.
interface *debug_handle_interim = static_cast<interface*>(new debug_interface());
// Convert a interface* to void* then back to interface*, see #2
void *type_erased = static_cast<void*>(debug_handle_interim);
interface *debug_handle_back = static_cast<interface*>(type_erased);
答案 1 :(得分:1)
你是对的,它是未定义的。当你转回void*
时,你应该始终使用原始指针类型。您应该使用static_cast
代替dynamic_cast
。
因此,您的代码可以安全地编写为:
int main(int argc, char *argv[]){
void *handle = new interface();
void *debug_handle = static_cast<interface*>(new debug_interface());
//Beware! This would be wrong:
//void *debug_handle = new debug_interface();
interface *foo1 = static_cast<interface*>(handle);
interface *foo2 = static_cast<interface*>(debug_handle);
foo1->foo();
foo2->foo();
return 0;
}
事实上,如果你碰巧有一个类,如:
class weird_interface : something, public interface
{ /* */};
然后写:
weird_interface *a = new weird_interface();
interface *b = static_cast<interface*>(static_cast<void*>(a));
interface *c = a;
static_assert(b == c, "");
你会明白UB的原因。
答案 2 :(得分:1)
标准不允许这样做。存储指针的正确方法是:
interface* tmp = new debug_interface;
void* handle = reinterpret_cast<void*>(tmp);
你当然可以在一行中完成:
void* handle = reinterpret_cast<void*>(
static_cast<interface*>(new debug_interface));
但这太笨重了,容易出错。
标准允许您将指针转换为void*
并返回,但您必须将其强制转换为您已启动的完全相同的指针类型。指向基类的指针是无可替代的。
如果使debug_interface
使用多个或虚拟继承,您的代码很有可能崩溃和刻录,但即使使用简单的单一依赖,它也不符合。
答案 3 :(得分:0)
你是对的,这个代码通常会破坏一个类的第二个接口。