将类型动态转换为类型的安全方法吗?

时间:2019-03-05 15:40:20

标签: c++ dynamic-cast

我的C ++有点生锈,我不记得标准中的所有内容。

我有一个void*。在一个特定的函数中,它要么是继承Alpha的类,要么是继承Beta的类。这两个基类都具有虚函数。但是我似乎无法分辨是哪个

class Alpha {
public:
    virtual void Speak() { printf("A"); }
};
class Beta {
public:
    virtual void Speak() { printf("B"); }
};
int main(){
    //BAD CODE WILL PRINT TWICE
    void *p = new Alpha;
    Alpha*a = dynamic_cast<Alpha*>((Alpha*)p);
    Beta*b = dynamic_cast<Beta*>((Beta*)p);
    if(a)
        a->Speak();
    if(b)
        b->Speak();
    return 0;
}

我如何确定哪个班级是哪个班级?此代码库中有100多个类,它们被转换为void。他们中的大多数继承了5个基类,但是我不急于发现。使用动态强制转换之前,唯一的解决方案是否继承自class Dummy {public: virtual void NoOp(){}};之类并强制转换为Dummy?这样安全吗?我希望有更好的解决方案,但我别无选择。

5 个答案:

答案 0 :(得分:5)

使用void*指针只能做的 操作是将其强制转换回精确,其类型与强制转换为{{ 1}}。未完成其他任何操作的行为。

您可以做的就是定义

void*

,并将其作为class Base { public: virtual ~Base() = default; // make me a polymorphic type and make // polymorphic delete safe at the same time. }; Alpha的基类。然后传递一个Beta指针而不是一个Base*指针,并将void*直接放在dynamic_cast上。

请进一步注意,如果您在p中声明了virtual void Speak() = 0;,那么在Base中的代码将变得简单

main

根据经验,任何形式的演员转换都是不可取的。

答案 1 :(得分:1)

表达式Alpha*a = dynamic_cast<Alpha*>((Alpha*)p);首先用explicit c style castp强制转换为Alpha*。然后,所得的Alpha*将通过dynamic_cast<Alpha*>传递。在dynamic_cast<T*>指针(与您尝试转换为相同类型的指针)上使用T*将始终提供输入指针。它不能用于确认指针有效。从cppreferencedynamic_cast<new_type>(expression)

  

如果expression的类型恰好是new_typenew_type的CV限定版本,则结果是expression的值,类型为{{1 }}。

因此,代码将始终编译并运行,并且类型系统不会保护您。但是结果是不确定的。对于new_type,您告诉编译器信任Beta*b = dynamic_cast<Beta*>((Beta*)p);p,但这不是事实。取消引用结果指针是未定义的行为,Beta*无法保护您免受此错误的影响。

如果尝试删除显式类型转换,则会出现编译器错误。 dynamic_cast需要一个指向完整类型的指针或引用,而dynamic_cast不是完整类型。您将必须找到一种方法来跟踪指向您自己的实际类型,并在使用void之前将p显式转换为该指针类型。尽管到那时,如果您已经知道要转换的类型,则可能不再需要。

考虑使用通用基本类型,或者在需要时使用std::variantstd::any

答案 2 :(得分:1)

如果在使用动态强制转换之前使用C样式强制转换为Alpha*(类似于static_cast),则动态强制转换无效。在这里运行您的代码是因为两个类具有相同的接口,但实际上这是未定义的行为。

通常,您要使用动态类型转换将基类向上/向下转换到派生类。

例如,如果我们添加一个基本接口,然后将void *指针转换为该基类,然后使用动态强制转换尝试进行向上转换,则该代码将按预期工作,并且仅打印一次。

#include <stdio.h>

class Speaker {
public:
  virtual void Speak() = 0;
};

class Alpha: public Speaker {
public:
  virtual void Speak() { printf("A"); }
};

class Beta: public Speaker {
public:
  virtual void Speak() { printf("B"); }
};

int main(){
  void *p = new Alpha;
  // Convert to base type, using static_cast                                    
  Speaker *s = static_cast<Speaker *>(p);
  // Attempt Upcasts                                                            
  Alpha*a = dynamic_cast<Alpha*>(s);
  Beta*b = dynamic_cast<Beta*>(s);

  // See if it worked                                                           
  if (a)
    a->Speak();
  if (b)
    b->Speak();
  return 0;
}

输出:A

答案 3 :(得分:0)

您必须知道void指针从其转换的类型。如果您不知道动态类型,则必须从指向基础的指针创建void指针。如果您不知道创建空指针的指针的类型,则不能使用空指针。

鉴于void指针是从Alpha*转换而来的,可以使用静态强制转换将其转换回去:

auto a_ptr = static_cast<Alpha*>(p);

然后可以使用dynamic_cast转换为派生类型。

if(auto d_ptr = dynamic_cast<DerivedAlpha*>(a_ptr)) {
    // do stuff with DerivedAlpha

如果动态类型不是DerivedAlpha,则动态类型转换将安全地返回null。您不能动态地抛弃类型层次结构。由于AlphaBeta没有任何继承结构的关联,因此它们不能相互动态地强制转换。

答案 4 :(得分:0)

我认为这可以比已经说的更简单地解释。

基本上,从原始void *进行强制转换,dynamic_cast的“智能”都无法应用,因为它没有任何作用,它只给您返回相同的指针。您需要具有一些共享的父类类型或某种类型的指针类型(* p),以便它知道如何读取内存并确定实际类型。

在您的情况下,您“幸运地”知道Alpha和Beta的内存布局是相同的,因此在Beta *上调用talk最终会调用Alpha :: speak()。正如其他人所说的,这是UB,如果类的差异更大,则可能会导致段错误或破坏堆栈。

相关问题