我的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?这样安全吗?我希望有更好的解决方案,但我别无选择。
答案 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 cast将p
强制转换为Alpha*
。然后,所得的Alpha*
将通过dynamic_cast<Alpha*>
传递。在dynamic_cast<T*>
指针(与您尝试转换为相同类型的指针)上使用T*
将始终提供输入指针。它不能用于确认指针有效。从cppreference到dynamic_cast<new_type>(expression)
:
如果
expression
的类型恰好是new_type
或new_type
的CV限定版本,则结果是expression
的值,类型为{{1 }}。
因此,代码将始终编译并运行,并且类型系统不会保护您。但是结果是不确定的。对于new_type
,您告诉编译器信任Beta*b = dynamic_cast<Beta*>((Beta*)p);
是p
,但这不是事实。取消引用结果指针是未定义的行为,Beta*
无法保护您免受此错误的影响。
如果尝试删除显式类型转换,则会出现编译器错误。 dynamic_cast
需要一个指向完整类型的指针或引用,而dynamic_cast
不是完整类型。您将必须找到一种方法来跟踪指向您自己的实际类型,并在使用void
之前将p
显式转换为该指针类型。尽管到那时,如果您已经知道要转换的类型,则可能不再需要。
考虑使用通用基本类型,或者在需要时使用std::variant
或std::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。您不能动态地抛弃类型层次结构。由于Alpha
和Beta
没有任何继承结构的关联,因此它们不能相互动态地强制转换。
答案 4 :(得分:0)
我认为这可以比已经说的更简单地解释。
基本上,从原始void *进行强制转换,dynamic_cast的“智能”都无法应用,因为它没有任何作用,它只给您返回相同的指针。您需要具有一些共享的父类类型或某种类型的指针类型(* p),以便它知道如何读取内存并确定实际类型。
在您的情况下,您“幸运地”知道Alpha和Beta的内存布局是相同的,因此在Beta *上调用talk最终会调用Alpha :: speak()。正如其他人所说的,这是UB,如果类的差异更大,则可能会导致段错误或破坏堆栈。