虚函数并转换为void和back

时间:2015-08-21 07:30:57

标签: c++ virtual void reinterpret-cast static-cast

目前我正在使用传统的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;}
};

对象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强制转换为接口*(而不是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更严格,并恢复其原始类型的产量   原始指针值。任何其他此类指针的结果   转换未指定。

handlefoo1的转换应该没问题,但我可以再次使用static_cast吗?

修改 我的示例源代码错误。 debug_interface是派生类的接口。

4 个答案:

答案 0 :(得分:4)

免责声明: 第一部分是在两个接口未通过继承关联时编写的。

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

   foo2->foo();

在这里,您在指向实现此API的对象的指针上使用interface API。 interfacedebug_interface碰巧实现foo()成员作为第一种方法的事实并没有改变任何东西:这些类与继承无关,因此它们不兼容。

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

编辑:如果debug_interface公开来自interface,则这是一个不同的问题。

在这种情况下,从debug_interface*转换为interface*是完全安全的: derived-to-base 转换甚至可以由编译器隐式应用。 但为了安全起见,必须通过以下任一方式直接完成演员表

  • a static_cast
  • 一个dynamic_cast(会引入运行时检查,对于向上播放来说是过度杀伤)。

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

  

指向对象的类型指针的值转换为“指向cv void的指针”并返回原始指针类型将具有其原始值。

在您的示例中,您不是转换回原始指针,而是转换为另一种指针类型:标准不会保证您将获得的值。

可能的解决方案

知道:

  1. 直接从debug_interface转换为interface
  2. 是安全的
  3. 将指针转换为对象类型为void *然后返回指向同一对象类型的指针是安全的。
  4. 您可以组装它以获得标准的保证解决方案:

    // 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)

你是对的,这个代码通常会破坏一个类的第二个接口。