虚拟功能并投入无效并返回
目前我正在使用遗留的c ++代码库。 在这个代码库中,指向对象的指针被转换为空指针,然后存储在一个c库中。 考虑下面的代码:
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
分配在堆上,地址存储到一个空指针。 在某一时刻,指针被检索出来,然后被转换回基类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当“指向T1的指针”类型的prvalue v被转换为类型“指向cv T2的指针”时,结果为static_cast(static_cast(v ))如果T1和T2都是标准布局类型(3.9),并且T2的对齐要求不比T1更严格。 将“指向T1的指针”类型的prvalue转换为类型“指向T2的指针”(其中T1和T2是对象类型,并且T2的对齐要求不比T1的对齐要求更严格)并返回到其原始类型,指针值。 任何其他此类指针转换的结果都未指定。
从handle
到foo1
的转换应该没问题,但我可以再次使用static_cast?
编辑我的示例源代码是错误的。 debug_interface是一个派生的接口类。
免责声明:第一部分是在两个接口不通过继承关联时编写的。
未定义的行为实际发生在这里:
foo2->foo();
在此,您正在使用interface
API指向未实现此API的对象。 interface
和debug_interface
碰巧实现了foo()
成员,因为它们的第一个方法不会改变任何东西:这些类不是通过继承关联的,所以它们不兼容。
您引用的摘录将讨论允许转换本身的情况。 在你的情况下,我的理解是,你实际上可以将一个指向debug_interface
的指针转换为一个指向interface
的指针:然而,你现在可以用指针指向接口的唯一安全的事情就是将它转换回debug_interface
指针:使用它来访问interface
成员是不安全的。
编辑:如果debug_interface
公开从interface
派生,这是一个不同的问题。
在这种情况下,从debug_interface*
为interface*
是完全安全的:编译器可以隐式应用派生到基本的转换。 为了安全起见,这个演员必须通过以下任一方式直接完成:
static_cast
dynamic_cast
(它将引入运行时检查,对于upcast来说是过度的)。 通过两次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);
你是对的,它是未定义的。 当你抛出一个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。
这是标准所不允许的。 存储指针的正确方法是:
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
使用多重或虚拟继承,您的代码很有可能会崩溃并烧毁,但即使使用普通单一inhetitance,它也不符合要求。