将 void 动态转换为类型的安全方法?
Safe way to dynamic cast void to a type?
我的 C++ 有点生疏,我不记得标准中的所有内容。
我有一个void*
。在一个特定函数中,它是继承 Alpha 的 class 或继承 Beta 的函数。两个基本 classes 都有虚函数。但是我好像分不清哪个是哪个
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;
}
我如何找出哪个 class 是哪个?此代码库中有 100 个 classes 被转换为 void。他们中的大多数继承了 5 个基 classes 但是我并不急于找出答案。唯一的解决方案是从 class Dummy {public: virtual void NoOp(){}};
之类的东西继承并在使用动态转换之前转换为 Dummy 吗?这样安全吗?我希望有更好的解决方案,但我想不出别的。
您必须知道 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
,动态转换将安全地 return null。您不能从类型层次结构中动态转换。由于 Alpha
和 Beta
没有任何继承结构相关,因此它们不能相互动态转换。
表达式 Alpha*a = dynamic_cast<Alpha*>((Alpha*)p);
首先将 p
转换为 Alpha*
,并使用 explicit c style cast. Then, that resulting Alpha*
is passed through dynamic_cast<Alpha*>
. Using dynamic_cast<T*>
on a T*
pointer (a pointer of the same type as you are trying to cast to) will always provide the input pointer. It cannot be used to confirm that the pointer is valid. From cppreference for dynamic_cast<new_type>(expression)
:
If the type of expression
is exactly new_type
or a less cv-qualified version of new_type
, the result is the value of expression
, with type new_type
.
因此,代码将始终编译并且 运行 并且类型系统不会保护您。但由此产生的行为是不确定的。在 Beta*b = dynamic_cast<Beta*>((Beta*)p);
的情况下,您告诉编译器相信 p
是 Beta*
,但事实并非如此。取消引用结果指针是未定义的行为,dynamic_cast
无法保护您免受此错误的影响。
如果您尝试删除显式类型转换,您将遇到编译器错误。 dynamic_cast
需要指向完整类型的指针或引用,而 void
不是完整类型。您必须找到一种方法来跟踪指向您自己的实际类型,并在使用 dynamic_cast
之前将 p
显式转换为该指针类型。不过到那时,如果您已经知道要转换为的类型,则可能不再需要它了。
考虑改用通用基类型,或者如果需要可以使用 std::variant
or std::any
。
唯一你可以用void*
指针做的事情是将它转换回完全相同的类型首先被转换为 void*
的指针。做 anything else 的行为是未定义的。
在你的情况下你可以做的是定义
class Base
{
public:
virtual ~Base() = default; // make me a polymorphic type and make
// polymorphic delete safe at the same time.
};
并将其设为 Alpha
和 Beta
的基础 class。然后传递一个 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
}
根据经验,任何类型的转换都是不可取的。
如果使用 C 风格转换为 Alpha*
,类似于使用动态转换之前的 static_cast,则动态转换无效。在这里你的代码运行是因为两个 classes 有相同的接口,但实际上这是未定义的行为。
通常,您想使用动态转换到 upcast/downcast from/to 一个基础 class to/from 它是派生的 class。
例如,如果我们添加一个基接口,然后将 void *
指针转换为该基 class,然后使用动态转换尝试向上转换,代码将按预期工作,并且仅打印一次。
#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* 转换,dynamic_cast 的“智能”none 可以应用,因为它没有任何可继续的,它只是给你返回相同的指针。您需要有一些共享父 class 类型或其他类型作为您的指针类型 (*p),以便它知道如何读取内存并确定实际类型。
在您的情况下,您“很幸运”Alpha 和 Beta 的内存布局相同,因此在 Beta* 上调用 speak* 最终会调用 Alpha::speak()。正如其他人所说,这是 UB,如果 classes 更不同,你可能会 seg-fault 或破坏堆栈。
我的 C++ 有点生疏,我不记得标准中的所有内容。
我有一个void*
。在一个特定函数中,它是继承 Alpha 的 class 或继承 Beta 的函数。两个基本 classes 都有虚函数。但是我好像分不清哪个是哪个
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;
}
我如何找出哪个 class 是哪个?此代码库中有 100 个 classes 被转换为 void。他们中的大多数继承了 5 个基 classes 但是我并不急于找出答案。唯一的解决方案是从 class Dummy {public: virtual void NoOp(){}};
之类的东西继承并在使用动态转换之前转换为 Dummy 吗?这样安全吗?我希望有更好的解决方案,但我想不出别的。
您必须知道 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
,动态转换将安全地 return null。您不能从类型层次结构中动态转换。由于 Alpha
和 Beta
没有任何继承结构相关,因此它们不能相互动态转换。
表达式 Alpha*a = dynamic_cast<Alpha*>((Alpha*)p);
首先将 p
转换为 Alpha*
,并使用 explicit c style cast. Then, that resulting Alpha*
is passed through dynamic_cast<Alpha*>
. Using dynamic_cast<T*>
on a T*
pointer (a pointer of the same type as you are trying to cast to) will always provide the input pointer. It cannot be used to confirm that the pointer is valid. From cppreference for dynamic_cast<new_type>(expression)
:
If the type of
expression
is exactlynew_type
or a less cv-qualified version ofnew_type
, the result is the value ofexpression
, with typenew_type
.
因此,代码将始终编译并且 运行 并且类型系统不会保护您。但由此产生的行为是不确定的。在 Beta*b = dynamic_cast<Beta*>((Beta*)p);
的情况下,您告诉编译器相信 p
是 Beta*
,但事实并非如此。取消引用结果指针是未定义的行为,dynamic_cast
无法保护您免受此错误的影响。
如果您尝试删除显式类型转换,您将遇到编译器错误。 dynamic_cast
需要指向完整类型的指针或引用,而 void
不是完整类型。您必须找到一种方法来跟踪指向您自己的实际类型,并在使用 dynamic_cast
之前将 p
显式转换为该指针类型。不过到那时,如果您已经知道要转换为的类型,则可能不再需要它了。
考虑改用通用基类型,或者如果需要可以使用 std::variant
or std::any
。
唯一你可以用void*
指针做的事情是将它转换回完全相同的类型首先被转换为 void*
的指针。做 anything else 的行为是未定义的。
在你的情况下你可以做的是定义
class Base
{
public:
virtual ~Base() = default; // make me a polymorphic type and make
// polymorphic delete safe at the same time.
};
并将其设为 Alpha
和 Beta
的基础 class。然后传递一个 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
}
根据经验,任何类型的转换都是不可取的。
如果使用 C 风格转换为 Alpha*
,类似于使用动态转换之前的 static_cast,则动态转换无效。在这里你的代码运行是因为两个 classes 有相同的接口,但实际上这是未定义的行为。
通常,您想使用动态转换到 upcast/downcast from/to 一个基础 class to/from 它是派生的 class。
例如,如果我们添加一个基接口,然后将 void *
指针转换为该基 class,然后使用动态转换尝试向上转换,代码将按预期工作,并且仅打印一次。
#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* 转换,dynamic_cast 的“智能”none 可以应用,因为它没有任何可继续的,它只是给你返回相同的指针。您需要有一些共享父 class 类型或其他类型作为您的指针类型 (*p),以便它知道如何读取内存并确定实际类型。
在您的情况下,您“很幸运”Alpha 和 Beta 的内存布局相同,因此在 Beta* 上调用 speak* 最终会调用 Alpha::speak()。正如其他人所说,这是 UB,如果 classes 更不同,你可能会 seg-fault 或破坏堆栈。