将空隙动态铸造到类型的安全方法?

Safe way to dynamic cast void to a type?

本文关键字:类型 安全 方法 动态      更新时间:2023-10-16

我的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(){}};和 cast 到 Dummy?这安全吗?我希望有更好的解决方案,但我想不出其他任何事情。

void*指针唯一能做的就是将其强制转换为与最初强制转换为void*的指针完全相同的类型。任何其他事情的行为是不确定的。

在这种情况下,您可以做的是定义

class Base
{
public:
virtual ~Base() = default; // make me a polymorphic type and make 
// polymorphic delete safe at the same time.
};

并将其作为AlphaBeta的基类。然后传递一个Base*指针而不是void*指针,并将dynamic_cast直接放在p上。

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

int main(){ 
Base* p = new Alpha;
p->Speak();
delete p; // ToDo - have a look at std::unique_ptr
}

根据经验,任何类型的演员都是不可取的。

Alpha*a = dynamic_cast<Alpha*>((Alpha*)p);表达式首先使用显式 c 样式强制转换pAlpha*。然后,生成的Alpha*通过dynamic_cast<Alpha*>传递。在T*指针(与尝试投射到的指针类型相同的指针)上使用dynamic_cast<T*>将始终提供输入指针。它不能用于确认指针是否有效。从 cpp 对dynamic_cast<new_type>(expression)的偏好

如果expression的类型正好是new_type或不太符合 cv 条件的new_type版本,则结果是expression的值,类型为new_type

因此,代码将始终编译和运行,类型系统不会保护你。但由此产生的行为是未定义的。在Beta*b = dynamic_cast<Beta*>((Beta*)p);的情况下,您告诉编译器信任p是一个Beta*但事实并非如此。取消引用生成的指针是未定义的行为,dynamic_cast无法保护您免受此错误的影响。

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

请考虑改用通用基类型,或者如果需要,可以使用std::variantstd::any

如果使用 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

您必须知道 void 指针的转换类型。如果不知道动态类型,则必须创建从指针到基的 void 指针。如果您不知道创建 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没有任何继承结构相关,因此它们不能相互动态转换。

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

基本上,从原始虚空*进行铸造,dynamic_cast的任何"智能"都无法应用,因为它没有什么可继续的,它只是给你同样的指针。你需要有一些共享的父类类型或其他东西作为你的指针类型(*p),这样它就知道如何读取内存并确定实际类型。

在您的情况下,您"幸运地"发现 Alpha 和 Beta 的内存布局是相同的,因此在 Beta* 上调用 speak 最终会调用 Alpha::speak()。正如其他人所说,这是UB,如果类更不同,您可能会出现段错误或损坏堆栈。