将空隙动态铸造到类型的安全方法?
Safe way to dynamic cast void to a type?
我的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.
};
并将其作为Alpha
和Beta
的基类。然后传递一个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 样式强制转换p
到Alpha*
。然后,生成的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::variant
或std::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。不能从类型层次结构中动态强制转换。由于Alpha
和Beta
没有任何继承结构相关,因此它们不能相互动态转换。
我觉得这可以比已经说的更简单地解释。
基本上,从原始虚空*进行铸造,dynamic_cast的任何"智能"都无法应用,因为它没有什么可继续的,它只是给你同样的指针。你需要有一些共享的父类类型或其他东西作为你的指针类型(*p),这样它就知道如何读取内存并确定实际类型。
在您的情况下,您"幸运地"发现 Alpha 和 Beta 的内存布局是相同的,因此在 Beta* 上调用 speak 最终会调用 Alpha::speak()。正如其他人所说,这是UB,如果类更不同,您可能会出现段错误或损坏堆栈。
- 为表示一个或多个操作的C++函数的int参数寻找类型安全的替换
- 附加类型安全的子类std::string
- 什么是 c/c++ 中的类型安全
- 在模板类中使用'new'类型安全吗?
- 类型安全可变参数函数
- 类型安全 - all_of/ any_of/ none_of for std::tuple
- 如何在具有模板函数的类中使用类型安全的联合(变体)
- 将自动类型变量初始化为零.这种类型安全吗?
- 在 C++ 中强制实施类型安全,而无需使用额外的类
- 用于memcpy的类型安全C 包装器
- 以更健壮和类型安全的方式处理ASCII命令
- 是否有任何类型安全的、编译时检查的 printf 的实现
- 如何在C++中使类型安全字符串"definition"对象?
- 以最新的C 的类型安全方式从枚举中随机选择元素
- 新的类型安全枚举是否定义为从 0 开始
- C++(类似SBT)的类型安全构建系统
- 初始值设定项列表引用类型安全
- 类型安全的c++11枚举类标志的模板
- valarray<valarray<内置类型> >安全吗?
- 具有按类型安全查找的可变大小异构容器