虚函数和强制转换为void和返回

Virtual functions and cast to void and back

本文关键字:void 转换 返回 函数      更新时间:2023-10-16

目前我正在使用一个遗留的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;}
};

对象interfacedebug_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强制转换为interface*(而不是debug_interface*)并调用虚拟调用真的节省吗?根据c++标准,(5.2.10)这是未定义的行为:

指向对象的指针可以显式转换为指针到不同的对象类型当一个"指针"类型的右值v将"指向T1"类型转换为"指向cv T2"类型,结果为如果T1和T2同时存在,则static_cast(static_cast(v))标准布局类型(3.9)和对齐要求是T2并不比T1严格。类型的右值转换将"指向T1的指针"转换为"指向T2的指针"类型(其中T1和T2分别为)对象类型,其中T2的对齐要求没有比T1更严格),并恢复到原来的类型收益率原始指针值。任何其他此类指针的结果未指定转换。

handlefoo1的转换应该是可以的,但我可以再次使用static_cast?

编辑我的示例源代码是错误的。Debug_interface是interface的派生类。

免责声明: 第一部分是在两个接口没有通过继承关联的情况下编写的。

未定义的行为实际上发生在这里:

   foo2->foo();

在这里,您正在使用interface API在指向对象的指针上,该对象是而不是实现该API。事实上,interfacedebug_interface碰巧都将foo()成员实现为它们的第一个方法,这不会改变任何东西:这些类不是通过继承相关的,因此它们是不兼容的。

您引用的摘录处理了转换本身被允许的情况。在您的情况下,我的理解是,您实际上可以将指向debug_interface的指针转换为指向interface的指针:然而,您现在可以对指向接口的指针做的唯一安全的事情是将其转换回debug_interface指针:使用它访问interface成员是不安全的。


EDIT:如果debug_interface公开派生自interface,这是一个不同的问题。

在这种情况下,从debug_interface*强制转换到interface*是完全安全的:从派生到基的转换甚至可以由编译器隐式地应用。但是为了安全起见,必须直接进行强制转换,可以通过:
  • a static_cast
  • a dynamic_cast(这将引入运行时检查,对于向上转换来说是过度的)。

通过两个reinterpret_cast执行此操作是一种未定义的行为:它可能适用于单继承(在某些编译器上),但绝对不受标准的保证。
通过两个static_cast执行此操作也将是未定义行为。标准保证(强调我的):

指向对象的指针类型的值转换为"指向cv void的指针"并返回到原始指针类型将保持其原始值。

在你的例子中,你并没有转换回原来的指针,而是转换回另一种指针类型:标准并没有给你任何保证你将得到的值。

可能的解决方案知道:

  1. 直接从debug_interface转换为interface是安全的
  2. 从指向对象类型的指针转换为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使用多重继承或虚拟继承,那么您的代码很有可能崩溃和燃烧,但即使使用普通的单一继承,它也不符合。

您是对的,这段代码通常在类的第二个接口时中断。